package com.hww.common.util;

import android.Manifest;
import android.content.ContentValues;
import android.content.Context;
import android.content.pm.PackageManager;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.Bitmap.CompressFormat;
import android.graphics.BitmapFactory;
import android.graphics.BitmapShader;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.ColorMatrix;
import android.graphics.ColorMatrixColorFilter;
import android.graphics.ImageFormat;
import android.graphics.LinearGradient;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PixelFormat;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffColorFilter;
import android.graphics.PorterDuffXfermode;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.Shader;
import android.graphics.YuvImage;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.media.ExifInterface;
import android.net.Uri;
import android.os.Build;
import android.os.Environment;
import android.provider.MediaStore;
import android.renderscript.Allocation;
import android.renderscript.Element;
import android.renderscript.RenderScript;
import android.renderscript.ScriptIntrinsicBlur;
import android.renderscript.ScriptIntrinsicYuvToRGB;
import android.renderscript.Type;
import android.text.TextUtils;
import android.util.Base64;
import android.util.Log;
import android.view.View;

import androidx.annotation.ColorInt;
import androidx.annotation.DrawableRes;
import androidx.annotation.FloatRange;
import androidx.annotation.IntRange;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import androidx.core.content.ContextCompat;

import com.hww.common.MyAppThis;

import java.io.BufferedOutputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileDescriptor;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

public class ImageUtils {

    /**
     * Base64转Bitmap
     * 在在线网站中验证：https://tool.jisuapi.com/base642pic.html
     * 同时部分转换需要添加前缀信息： data:image/png;base64,
     *
     * @param base64String base64编码的字符串，需要确定是否有头编码(data:image/png;base64,)，需要分离头编码 str.split(",")[1]
     * @return Bitmap对象
     */
    public static Bitmap base642Bitmap(String base64String) {
        return base642Bitmap(base64String, Base64.DEFAULT);
    }

    /**
     * Base64转Bitmap, 默认使用Base64.DEFAULT | Base64.NO_WRAP (组合使用：标准的Base64字符集与不包含换行符)
     * 在在线网站中验证：https://tool.jisuapi.com/base642pic.html
     * 同时部分转换需要添加前缀信息： data:image/png;base64,
     *
     * @param base64String base64编码的字符串，需要确定是否有头编码(data:image/png;base64,)，需要分离头编码 str.split(",")[1]
     * @return Bitmap对象
     */
    public static Bitmap base642BitmapDefault(String base64String) {
        return base642Bitmap(base64String, Base64.DEFAULT | Base64.NO_WRAP);
    }

    /**
     * 将Base64编码的字符串转换为Bitmap图像。
     * 此方法首先将Base64编码的字符串解码为字节数组，然后将该字节数组转换为Bitmap对象。
     *
     * @param base64String Base64编码的字符串。
     * @param flags        Base64编码选项的标志集合。可以使用不同的标志来控制编码过程的行为，
     *                     {@link Base64#DEFAULT} 输出的Base64字符串使用标准的Base64字符集，
     *                     包括字母、数字、加号和斜杠,可能包含换行符，以使字符串长度不超过 76 个字符。这有助于在传输时分割较长的字符串
     *                     {@link Base64#NO_WRAP} 输出的 Base64 字符串不含换行符
     *                     {@link Base64#NO_PADDING} 输出的 Base64 字符串不包含填充字符，因此编码后的字符串长度不一定能被4整除
     *                     {@link Base64#URL_SAFE} 会将标准的 Base64 字符集中的加号（+）和斜杠（/）替换为减号（-）和下划线（_），以便在 URL 或文件名中使用
     *                     组合使用（例如：Base64.NO_PADDING | Base64.URL_SAFE）:当需要结合多个选项以满足特定需求，比如既不含填充字符又需要 URL 安全
     * @return 转换后的Bitmap对象，如果输入的Base64字符串为null，则返回null。
     */
    public static Bitmap base642Bitmap(String base64String, int flags) {
        if (null == base64String) return null;
        byte[] decode = Base64.decode(base64String, flags);
        return bytes2Bitmap(decode);
    }

    /**
     * 将Bitmap对象转换为Base64编码的字符串。
     * 此方法首先将Bitmap转换为字节数组，然后将这个字节数组编码为Base64字符串。
     * 使用Base64编码可以将二进制数据转换为可打印的字符形式，便于传输和存储。
     *
     * @param bitmap 要转换的Bitmap对象。
     * @param flags  Base64编码选项的标志集合。可以使用不同的标志来控制编码过程的行为，
     *               {@link Base64#DEFAULT} 输出的Base64字符串使用标准的Base64字符集，
     *               包括字母、数字、加号和斜杠,可能包含换行符，以使字符串长度不超过 76 个字符。这有助于在传输时分割较长的字符串
     *               {@link Base64#NO_WRAP} 输出的 Base64 字符串不含换行符
     *               {@link Base64#NO_PADDING} 输出的 Base64 字符串不包含填充字符，因此编码后的字符串长度不一定能被4整除
     *               {@link Base64#URL_SAFE} 会将标准的 Base64 字符集中的加号（+）和斜杠（/）替换为减号（-）和下划线（_），以便在 URL 或文件名中使用
     *               组合使用（例如：Base64.NO_PADDING | Base64.URL_SAFE）:当需要结合多个选项以满足特定需求，比如既不含填充字符又需要 URL 安全
     * @return 返回Base64编码后的字符串。
     */
    public static String bitmap2Base64(Bitmap bitmap, int flags) {
        //将Bitmap转换成字符串
        byte[] bytes = bitmap2Bytes(bitmap);
        return Base64.encodeToString(bytes, flags);
    }

    /**
     * 将Bitmap对象转换为Base64编码的字符串。
     * 在在线网站中验证：https://tool.jisuapi.com/pic2base64.html
     * 同时部分转换需要添加前缀信息： data:image/png;base64,
     *
     * @param bitmap 要转换的Bitmap对象。
     * @return 返回Base64编码后的字符串。
     */
    public static String bitmap2Base64(Bitmap bitmap) {
        return bitmap2Base64(bitmap, Base64.DEFAULT);
    }

    /**
     * 将Bitmap对象转换为Base64编码的字符串,默认使用Base64.DEFAULT | Base64.NO_WRAP (组合使用：标准的Base64字符集与不包含换行符)
     * 在在线网站中验证：https://tool.jisuapi.com/pic2base64.html
     * 同时部分转换需要添加前缀信息： data:image/png;base64,
     *
     * @param bitmap 要转换的Bitmap对象。
     * @return 返回Base64编码后的字符串。
     */
    public static String bitmap2Base64Default(Bitmap bitmap) {
        return bitmap2Base64(bitmap, Base64.DEFAULT | Base64.NO_WRAP);
    }

    /**
     * byte[] data保存的是纯RGB的数据，而非完整的图片文件数据
     */
    public static Bitmap createMyBitmap(byte[] data, int width, int height) {
        int[] colors = convertByteToColor(data);
        if (colors == null) {
            return null;
        }
        Bitmap bmp = null;
        try {
            bmp = Bitmap.createBitmap(colors, 0, width, width, height, Bitmap.Config.ARGB_8888);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }

        return bmp;
    }

    /**
     * 将一个byte数转成int
     * 实现这个函数的目的是为了将byte数当成无符号的变量去转化成int
     *
     * @param data BGR数据
     * @return
     */
    private static int convertByteToInt(byte data) {

        int heightBit = (int) ((data >> 4) & 0x0F);
        int lowBit = (int) (0x0F & data);
        return heightBit * 16 + lowBit;
    }

    //

    /**
     * 将纯BGR数据数组转化成int像素数组
     *
     * @param data BGR数据
     * @return
     */
    private static int[] convertByteToColor(byte[] data) {
        int size = data.length;
        if (size == 0) {
            return null;
        }
        int arg = 0;
        if (size % 3 != 0) {
            arg = 1;
        }
        // 一般RGB字节数组的长度应该是3的倍数，
        // 不排除有特殊情况，多余的RGB数据用黑色0XFF000000填充
        int[] color = new int[size / 3 + arg];
        int red, green, blue;
        int colorLen = color.length;
        if (arg == 0) {
            //BGR数据转RGBA
            for (int i = 0; i < colorLen; ++i) {
                red = convertByteToInt(data[i * 3 + 2]);
                green = convertByteToInt(data[i * 3 + 1]);
                blue = convertByteToInt(data[i * 3]);

                // 获取RGB分量值通过按位或生成int的像素值
                color[i] = (red << 16) | (green << 8) | blue | 0xFF000000;
            }
        } else {
            //RGB数据转RGBA
            for (int i = 0; i < colorLen - 1; ++i) {
                red = convertByteToInt(data[i * 3]);
                green = convertByteToInt(data[i * 3 + 1]);
                blue = convertByteToInt(data[i * 3 + 2]);
                color[i] = (red << 16) | (green << 8) | blue | 0xFF000000;
            }

            color[colorLen - 1] = 0xFF000000;
        }

        return color;
    }

    /**
     * 从字节数组data中解码出图片
     *
     * @param data       字节数组
     * @param mMaxWidth  最大宽度
     * @param mMaxHeight 最大高度
     * @return Bitmap
     */
    public static Bitmap getImageFromData(byte[] data, int mMaxWidth, int mMaxHeight) {
        BitmapFactory.Options decodeOptions = new BitmapFactory.Options();
        Bitmap bitmap = null;
        if (mMaxWidth == 0 && mMaxHeight == 0) {
            decodeOptions.inPreferredConfig = Bitmap.Config.RGB_565;
            bitmap = BitmapFactory.decodeByteArray(data, 0, data.length,
                    decodeOptions);
        } else {
            // If we have to resize this image, first get the natural bounds.
            decodeOptions.inJustDecodeBounds = true;
            BitmapFactory.decodeByteArray(data, 0, data.length, decodeOptions);
            int actualWidth = decodeOptions.outWidth;
            int actualHeight = decodeOptions.outHeight;

            // Then compute the dimensions we would ideally like to decode to.
            int desiredWidth = getResizedDimension(mMaxWidth, mMaxHeight,
                    actualWidth, actualHeight);
            int desiredHeight = getResizedDimension(mMaxHeight, mMaxWidth,
                    actualHeight, actualWidth);

            // Decode to the nearest power of two scaling factor.
            decodeOptions.inJustDecodeBounds = false;
            // TODO(ficus): Do we need this or is it okay since API 8 doesn't
            // support it?
            // decodeOptions.inPreferQualityOverSpeed =
            // PREFER_QUALITY_OVER_SPEED;
            decodeOptions.inSampleSize = findBestSampleSize(actualWidth,
                    actualHeight, desiredWidth, desiredHeight);
            Bitmap tempBitmap = BitmapFactory.decodeByteArray(data, 0,
                    data.length, decodeOptions);

            // If necessary, scale down to the maximal acceptable size.
            if (tempBitmap != null && (tempBitmap.getWidth() > desiredWidth || tempBitmap.getHeight() > desiredHeight)) {
                bitmap = Bitmap.createScaledBitmap(tempBitmap, desiredWidth, desiredHeight, true);
                tempBitmap.recycle();
            } else {
                bitmap = tempBitmap;
            }
        }
        return bitmap;
    }

    /**
     * 从文件中加载图片，并根据指定的最大宽度和高度进行缩放。
     *
     * @param file       图片文件。
     * @param mMaxWidth  最大宽度，如果为0则表示不限制。
     * @param mMaxHeight 最大高度，如果为0则表示不限制。
     * @return 缩放后的Bitmap对象，如果加载或缩放失败则返回null。
     */
    public static Bitmap getImageFromFile(File file, int mMaxWidth, int mMaxHeight) {
        BitmapFactory.Options decodeOptions = new BitmapFactory.Options();
        Bitmap bitmap = null;
        Bitmap.Config preferredConfig = Bitmap.Config.RGB_565;
        try {
            if (mMaxWidth == 0 && mMaxHeight == 0) {
                bitmap = BitmapFactory.decodeFile(file.getPath());
            } else {
                // 先设置inJustDecodeBounds为true，以获取图片的原始尺寸而不加载到内存
                decodeOptions.inJustDecodeBounds = true;
                decodeOptions.inPreferredConfig = preferredConfig;
                bitmap = BitmapFactory.decodeFile(file.getPath(), decodeOptions);
                int actualWidth = decodeOptions.outWidth;
                int actualHeight = decodeOptions.outHeight;

                // Then compute the dimensions we would ideally like to decode
                // to.
                int desiredWidth = getResizedDimension(mMaxWidth, mMaxHeight,
                        actualWidth, actualHeight);
                int desiredHeight = getResizedDimension(mMaxHeight, mMaxWidth,
                        actualHeight, actualWidth);

                // 设置inJustDecodeBounds为false，以真正加载图片到内存
                decodeOptions.inJustDecodeBounds = false;
                decodeOptions.inSampleSize = findBestSampleSize(actualWidth,
                        actualHeight, desiredWidth, desiredHeight);
                decodeOptions.inPreferredConfig = preferredConfig;
                Bitmap tempBitmap = BitmapFactory.decodeFile(file.getPath(),
                        decodeOptions);
                // If necessary, scale down to the maximal acceptable size.
                if (tempBitmap != null
                        && (tempBitmap.getWidth() > desiredWidth || tempBitmap
                        .getHeight() > desiredHeight)) {
                    bitmap = Bitmap.createScaledBitmap(tempBitmap,
                            desiredWidth, desiredHeight, true);
                    tempBitmap.recycle();
                } else {
                    bitmap = tempBitmap;
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return bitmap;
    }

    /**
     * 计算调整后的尺寸
     *
     * @param maxPrimary      最大主尺寸
     * @param maxSecondary    最大次尺寸
     * @param actualPrimary   实际主尺寸
     * @param actualSecondary 实际次尺寸
     * @return 调整后的主尺寸
     */
    public static int getResizedDimension(int maxPrimary, int maxSecondary,
                                          int actualPrimary, int actualSecondary) {
        // If no dominant value at all, just return the actual.
        if (maxPrimary == 0 && maxSecondary == 0) {
            return actualPrimary;
        }

        // If primary is unspecified, scale primary to match secondary's scaling
        // ratio.
        if (maxPrimary == 0) {
            double ratio = (double) maxSecondary / (double) actualSecondary;
            return (int) (actualPrimary * ratio);
        }

        if (maxSecondary == 0) {
            return maxPrimary;
        }

        double ratio = (double) actualSecondary / (double) actualPrimary;
        int resized = maxPrimary;
        if (resized * ratio > maxSecondary) {
            resized = (int) (maxSecondary / ratio);
        }
        return resized;
    }

    /**
     * 计算最佳采样大小，用于图像缩放。
     * 该方法通过比较实际图像尺寸与期望尺寸的比例，确定最佳的采样大小，以确保图像在缩放时能保持最佳的质量和性能平衡。
     *
     * @param actualWidth   实际图像的宽度。
     * @param actualHeight  实际图像的高度。
     * @param desiredWidth  期望图像的宽度。
     * @param desiredHeight 期望图像的高度。
     * @return 返回计算出的最佳采样大小。
     */
    public static int findBestSampleSize(int actualWidth, int actualHeight, int desiredWidth, int desiredHeight) {
        double wr = (double) actualWidth / desiredWidth;
        double hr = (double) actualHeight / desiredHeight;
        double ratio = Math.min(wr, hr);
        float n = 1.0f;
        while ((n * 2) <= ratio) {
            n *= 2;
        }

        return (int) n;
    }


    /**
     * 添加时间水印
     *
     * @param mBitmap
     * @return mNewBitmap
     */
    private static Bitmap getTimeWatermarkPhoto(Bitmap mBitmap) {
        //获取原始图片与水印图片的宽与高
        int mBitmapWidth = mBitmap.getWidth();
        int mBitmapHeight = mBitmap.getHeight();
        Bitmap mNewBitmap = Bitmap.createBitmap(mBitmapWidth, mBitmapHeight, Bitmap.Config.ARGB_8888);
        Canvas mCanvas = new Canvas(mNewBitmap);
        //向位图中开始画入MBitmap原始图片
        mCanvas.drawBitmap(mBitmap, 0, 0, null);
        //添加文字
        Paint mPaint = new Paint();
        String mFormat = DateUtils.getNowString();
        //String mFormat = TingUtils.getTime()+"\n"+" 纬度:"+GpsService.latitude+"  经度:"+GpsService.longitude;
        mPaint.setColor(Color.RED);
        mPaint.setTextSize(20);
        //水印的位置坐标
        mCanvas.drawText(mFormat, (mBitmapWidth * 1) / 10, (mBitmapHeight * 14) / 15, mPaint);
        /*mCanvas.save(Canvas.ALL_SAVE_FLAG);*/
        mCanvas.save();
        mCanvas.restore();

        return mNewBitmap;
    }


    /*--------------------------------------*/

    /**
     * 根据给定的宽和高进行拉伸
     *
     * @param origin    原图
     * @param newWidth  新图的宽
     * @param newHeight 新图的高
     * @return new Bitmap
     */
    private Bitmap scaleBitmap(Bitmap origin, int newWidth, int newHeight) {
        if (origin == null) {
            return null;
        }
        int height = origin.getHeight();
        int width = origin.getWidth();
        float scaleWidth = ((float) newWidth) / width;
        float scaleHeight = ((float) newHeight) / height;
        Matrix matrix = new Matrix();
        matrix.postScale(scaleWidth, scaleHeight);// 使用后乘
        Bitmap newBM = Bitmap.createBitmap(origin, 0, 0, width, height, matrix, false);
        if (!origin.isRecycled()) {
            origin.recycle();
        }
        return newBM;
    }

    /**
     * 按比例缩放图片
     *
     * @param origin 原图
     * @param ratio  比例
     * @return 新的bitmap
     */
    private Bitmap scaleBitmap(Bitmap origin, float ratio) {
        if (origin == null) {
            return null;
        }
        int width = origin.getWidth();
        int height = origin.getHeight();
        Matrix matrix = new Matrix();
        matrix.preScale(ratio, ratio);
        Bitmap newBM = Bitmap.createBitmap(origin, 0, 0, width, height, matrix, false);
        if (newBM.equals(origin)) {
            return newBM;
        }
        origin.recycle();
        return newBM;
    }

    /**
     * 偏移效果
     *
     * @param origin 原图
     * @return 偏移后的bitmap
     */
    private Bitmap skewBitmap(Bitmap origin) {
        if (origin == null) {
            return null;
        }
        int width = origin.getWidth();
        int height = origin.getHeight();
        Matrix matrix = new Matrix();
        matrix.postSkew(-0.6f, -0.3f);
        Bitmap newBM = Bitmap.createBitmap(origin, 0, 0, width, height, matrix, false);
        if (newBM.equals(origin)) {
            return newBM;
        }
        origin.recycle();
        return newBM;
    }

    /**
     * 从YUV数据转换为Bitmap图像。
     * <p>
     * 此方法通过YUV数据创建一个Bitmap对象。YUV是一种广泛使用的图像格式，主要用于摄像头预览数据和视频编码。
     * 该方法首先将YUV数据包装到YuvImage对象中，然后将其压缩为JPEG格式，最后使用BitmapFactory将JPEG数据解码为Bitmap。
     *
     * @param nv21Data YUV格式的图像数据。
     * @param width    图像的宽度。
     * @param height   图像的高度。
     * @return 返回从YUV数据创建的Bitmap对象。
     */
    public static Bitmap nv21ToBitmap(byte[] nv21Data, int width, int height) {
        YuvImage yuvimage = new YuvImage(nv21Data, ImageFormat.NV21, width, height, null);
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        yuvimage.compressToJpeg(new Rect(0, 0, width, height), 100, byteArrayOutputStream);
        byte[] toByteArray = byteArrayOutputStream.toByteArray();
        BitmapFactory.Options bitmapFactoryOptions = new BitmapFactory.Options();
        /*设置Bitmap的配置为RGB_565，这是一种常用的色彩配置，用于减少内存使用*/
        bitmapFactoryOptions.inPreferredConfig = Bitmap.Config.RGB_565;
        /*使用BitmapFactory从JPEG数据解码为Bitmap*/
        Bitmap bmp = BitmapFactory.decodeByteArray(toByteArray, 0, toByteArray.length, bitmapFactoryOptions);
        return bmp;
    }

    /**
     * 使用RenderScript将NV21格式的图像数据转换为Bitmap。
     * <p>
     * RenderScript是一种用于计算密集型任务的编程接口，适合处理图像数据转换等操作。
     * 本方法利用RenderScript的ScriptIntrinsicYuvToRGB内联脚本，将YUV 420 SP（NV21）格式的图像数据
     * 转换为ARGB_8888格式的Bitmap，适用于Android系统中对图像处理的需求。
     *
     * @param context  上下文对象，用于创建RenderScript实例。
     * @param nv21Data NV21格式的图像数据数组。
     * @param width    图像的宽度。
     * @param height   图像的高度。
     * @return 转换后的Bitmap对象，如果转换失败则返回null。
     */
    @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR1)
    public Bitmap nv21ToBitmapUsingRenderScript(Context context, byte[] nv21Data, int width, int height) {
        try {
            // 初始化RenderScript环境
            RenderScript rs = RenderScript.create(context);

            // 创建类型为NV21的Allocation对象，用于输入NV21数据
            Type.Builder yuvType = new Type.Builder(rs, Element.U8(rs)).setX(nv21Data.length);
            Allocation inAlloc = Allocation.createTyped(rs, yuvType.create(), Allocation.USAGE_SCRIPT);

            // 将NV21数据传入Allocation
            inAlloc.copyFrom(nv21Data);

            // 创建输出的Bitmap类型Allocation
            Type.Builder rgbaType = new Type.Builder(rs, Element.RGBA_8888(rs)).setX(width).setY(height);
            Allocation outAlloc = Allocation.createTyped(rs, rgbaType.create(), Allocation.USAGE_SCRIPT);

            // 创建并初始化YuvToRgb的Intrinsic Script
            ScriptIntrinsicYuvToRGB yuvToRgb = ScriptIntrinsicYuvToRGB.create(rs, Element.U8_4(rs));
            yuvToRgb.setInput(inAlloc);

            // 执行转换操作
            yuvToRgb.forEach(outAlloc);

            // 创建并返回Bitmap
            Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
            outAlloc.copyTo(bitmap);

            // 释放资源
            rs.destroy();

            return bitmap;
        } catch (Exception e) {
            Log.e("NV21ToBitmap", "Error converting NV21 to Bitmap", e);
            return null;
        }
    }


    public static void rotateNv21To90(byte[] src, byte[] des, int width, int height) {
        int wh = width * height;
        //旋转Y
        int k = 0;
        for (int i = 0; i < width; i++) {
            for (int j = 0; j < height; j++) {
                des[k] = src[width * (height - j - 1) + i];
                k++;
            }
        }
        for (int i = 0; i < width; i += 2) {
            for (int j = 0; j < height / 2; j++) {
                des[k] = src[wh + width * (height / 2 - j - 1) + i];
                des[k + 1] = src[wh + width * (height / 2 - j - 1) + i + 1];
                k += 2;
            }
        }
    }

    public static void rotateNv21To270(byte[] src, byte[] des, int width, int height) {
        int wh = width * height;
        //旋转Y
        int k = 0;
        for (int i = 0; i < width; i++) {
            for (int j = 0; j < height; j++) {
                des[k] = src[width * j + width - i - 1];
                k++;
            }
        }
        for (int i = 0; i < width; i += 2) {
            for (int j = 0; j < height / 2; j++) {
                des[k + 1] = src[wh + width * j + width - i - 1];
                des[k] = src[wh + width * j + width - (i + 1) - 1];
                k += 2;
            }
        }
    }


    private ImageUtils() {
        throw new UnsupportedOperationException("u can't instantiate me...");
    }

    /**
     * 将Bitmap对象转换为字节数组(图片的压缩质量为100)。
     *
     * @param bitmap 需要转换的Bitmap对象。如果为null，则方法直接返回null。
     * @return 返回转换后的字节数组。如果输入的bitmap为null，则返回null。
     */
    public static byte[] bitmap2Bytes(final Bitmap bitmap) {
        return bitmap2Bytes(bitmap, CompressFormat.PNG, 100);
    }

    /**
     * 将Bitmap对象转换为字节数组。
     * 此方法允许指定图片的压缩格式和质量，以控制转换后的字节数组大小。
     *
     * @param bitmap  需要转换的Bitmap对象。如果为null，则方法直接返回null。
     * @param format  图片的压缩格式。此参数决定了图片以何种方式压缩。
     * @param quality 图片的压缩质量。质量值越低，图片被压缩得越小。
     *                质量值的范围通常为0到100，但具体取决于压缩格式。
     * @return 返回转换后的字节数组。如果输入的bitmap为null，则返回null。
     */
    public static byte[] bitmap2Bytes(@Nullable final Bitmap bitmap, @NonNull final CompressFormat format, int quality) {
        if (bitmap == null) return null;
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        bitmap.compress(format, quality, baos);
        return baos.toByteArray();
    }

    /**
     * 将字节数组转换为Bitmap对象。
     * <p>
     * 此方法用于将通过某种方式获取的字节数组还原为对应的Bitmap对象，以供显示或进一步处理。
     * 如果字节数组为空或长度为0，则返回null，否则通过BitmapFactory的decodeByteArray方法解析字节数组。
     *
     * @param bytes 待转换的字节数组，可能为null。
     * @return 转换后的Bitmap对象，如果输入为null或空数组，则返回null。
     */
    public static Bitmap bytes2Bitmap(@Nullable final byte[] bytes) {
        return (bytes == null || bytes.length == 0)
                ? null
                : BitmapFactory.decodeByteArray(bytes, 0, bytes.length);
    }

    /**
     * 将Drawable对象转换为Bitmap。
     * 这个方法用于处理各种类型的Drawable对象，包括但不限于BitmapDrawable，将其转换为Bitmap格式。
     * 如果传入的Drawable为null，或者无法直接转换为Bitmap，方法将返回一个1x1像素的透明Bitmap。
     *
     * @param drawable 要转换的Drawable对象。
     * @return 转换后的Bitmap对象，如果转换失败或输入为null，则返回null。
     */
    public static Bitmap drawable2Bitmap(@Nullable final Drawable drawable) {
        if (drawable == null) return null;
        if (drawable instanceof BitmapDrawable) {
            BitmapDrawable bitmapDrawable = (BitmapDrawable) drawable;
            if (bitmapDrawable.getBitmap() != null) {
                return bitmapDrawable.getBitmap();
            }
        }
        Bitmap bitmap;
        if (drawable.getIntrinsicWidth() <= 0 || drawable.getIntrinsicHeight() <= 0) {
            bitmap = Bitmap.createBitmap(1, 1,
                    drawable.getOpacity() != PixelFormat.OPAQUE
                            ? Bitmap.Config.ARGB_8888
                            : Bitmap.Config.RGB_565);
        } else {
            bitmap = Bitmap.createBitmap(drawable.getIntrinsicWidth(),
                    drawable.getIntrinsicHeight(),
                    drawable.getOpacity() != PixelFormat.OPAQUE
                            ? Bitmap.Config.ARGB_8888
                            : Bitmap.Config.RGB_565);
        }
        Canvas canvas = new Canvas(bitmap);
        drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
        drawable.draw(canvas);
        return bitmap;
    }

    /**
     * 将Bitmap转换为Drawable对象。
     * 这个方法用于将位图对象转换为可以应用于UI元素的Drawable对象。如果提供的Bitmap为null，则返回null。
     *
     * @param bitmap 待转换的Bitmap对象，可能为null。
     * @return 转换后的Drawable对象，如果输入的Bitmap为null，则返回null。
     */
    public static Drawable bitmap2Drawable(@Nullable final Bitmap bitmap) {
        return bitmap == null ? null : new BitmapDrawable(MyAppThis.getThis().getResources(), bitmap);
    }

    /**
     * 将Drawable对象转换为Bitmap。
     * 这个方法用于处理各种类型的Drawable对象，包括但不限于BitmapDrawable，将其转换为Bitmap格式。
     * 如果传入的Drawable为null，或者无法直接转换为Bitmap，方法将返回一个1x1像素的透明Bitmap。
     *
     * @param drawable 要转换的Drawable对象。
     * @return 转换后的Bitmap对象，如果转换失败或输入为null，则返回null。
     */
    public static byte[] drawable2Bytes(@Nullable final Drawable drawable) {
        return drawable == null ? null : bitmap2Bytes(drawable2Bitmap(drawable));
    }

    /**
     * 将Drawable对象转换为字节数组。
     * 此方法允许指定图片的压缩格式和质量，以控制转换后的字节数组大小。
     *
     * @param drawable 需要转换的Drawable对象。如果为null，则方法直接返回null。
     * @param format   图片的压缩格式。此参数决定了图片以何种方式压缩。
     * @param quality  图片的压缩质量。质量值越低，图片被压缩得越小。
     *                 质量值的范围通常为0到100，但具体取决于压缩格式。
     * @return 返回转换后的字节数组。如果输入的drawable为null，则返回null。
     */
    public static byte[] drawable2Bytes(final Drawable drawable, final CompressFormat format, int quality) {
        return drawable == null ? null : bitmap2Bytes(drawable2Bitmap(drawable), format, quality);
    }

    /**
     * 将字节数组转换为Drawable对象。
     *
     * @param bytes 待转换的字节数组，可能为null。
     * @return 转换后的Drawable对象，如果输入的Bitmap为null，则返回null。
     */
    public static Drawable bytes2Drawable(final byte[] bytes) {
        return bitmap2Drawable(bytes2Bitmap(bytes));
    }

    /**
     * 将View转换为Bitmap。
     * 此方法首先尝试使用View的drawing cache来创建Bitmap，如果drawing cache不可用或已回收，
     * 则直接绘制View到一个新的Bitmap上。这种方法确保了即使在drawing cache不可用的情况下，
     * 也能成功将View转换为Bitmap。
     *
     * @param view 需要转换为Bitmap的View。
     * @return 转换后的Bitmap，如果view为null，则返回null。
     */
    public static Bitmap view2Bitmap(final View view) {
        if (view == null) return null;
        boolean drawingCacheEnabled = view.isDrawingCacheEnabled();
        boolean willNotCacheDrawing = view.willNotCacheDrawing();
        view.setDrawingCacheEnabled(true);
        view.setWillNotCacheDrawing(false);
        Bitmap drawingCache = view.getDrawingCache();
        Bitmap bitmap;
        if (null == drawingCache || drawingCache.isRecycled()) {
            view.measure(View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED),
                    View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED));
            view.layout(0, 0, view.getMeasuredWidth(), view.getMeasuredHeight());
            view.buildDrawingCache();
            drawingCache = view.getDrawingCache();
            if (null == drawingCache || drawingCache.isRecycled()) {
                bitmap = Bitmap.createBitmap(view.getMeasuredWidth(), view.getMeasuredHeight(), Bitmap.Config.RGB_565);
                Canvas canvas = new Canvas(bitmap);
                view.draw(canvas);
            } else {
                bitmap = Bitmap.createBitmap(drawingCache);
            }
        } else {
            bitmap = Bitmap.createBitmap(drawingCache);
        }
        view.setWillNotCacheDrawing(willNotCacheDrawing);
        view.setDrawingCacheEnabled(drawingCacheEnabled);
        return bitmap;
    }

    /**
     * 从文件加载Bitmap图像。
     *
     * @param file 图像文件。此文件不应为空。
     * @return 加载成功的Bitmap对象，如果文件无效或加载失败，则返回null。
     */
    public static Bitmap getBitmap(final File file) {
        if (file == null) return null;
        return BitmapFactory.decodeFile(file.getAbsolutePath());
    }

    /**
     * 根据文件、最大宽度和最大高度加载位图。
     * 此方法旨在优化位图加载，防止内存溢出，特别是对于大图片。
     * 它通过计算采样大小来减小加载图片的大小，从而在内存中加载一个更小的版本。
     *
     * @param file      图片文件。如果文件为null，则直接返回null。
     * @param maxWidth  图片加载后的最大宽度。
     * @param maxHeight 图片加载后的最大高度。
     * @return 加载后的位图。如果文件为null或者在加载过程中遇到错误，也可能返回null。
     */
    public static Bitmap getBitmap(final File file, final int maxWidth, final int maxHeight) {
        if (file == null) return null;
        BitmapFactory.Options options = new BitmapFactory.Options();
        options.inJustDecodeBounds = true;
        BitmapFactory.decodeFile(file.getAbsolutePath(), options);
        options.inSampleSize = calculateInSampleSize(options, maxWidth, maxHeight);
        options.inJustDecodeBounds = false;
        return BitmapFactory.decodeFile(file.getAbsolutePath(), options);
    }

    /**
     * 从文件路径加载Bitmap图像。
     *
     * @param filePath 图像文件的路径。此路径不应为空或仅包含空格。
     * @return 加载成功的Bitmap对象，如果文件路径无效或加载失败，则返回null。
     */
    public static Bitmap getBitmap(final String filePath) {
        if (StringUtils.isSpace(filePath)) return null;
        return BitmapFactory.decodeFile(filePath);
    }

    /**
     * 根据文件路径、最大宽度和最大高度加载位图。
     * 此方法旨在优化位图加载，防止因图片过大导致的内存问题。
     * 它通过计算采样大小来减小加载位图的内存占用。
     *
     * @param filePath  图片文件的路径。
     * @param maxWidth  图片加载后的最大宽度。
     * @param maxHeight 图片加载后的最大高度。
     * @return 加载后的位图对象，如果文件路径为空或无效，则返回null。
     */
    public static Bitmap getBitmap(final String filePath, final int maxWidth, final int maxHeight) {
        if (StringUtils.isSpace(filePath)) return null;
        BitmapFactory.Options options = new BitmapFactory.Options();
        options.inJustDecodeBounds = true;
        BitmapFactory.decodeFile(filePath, options);
        options.inSampleSize = calculateInSampleSize(options, maxWidth, maxHeight);
        options.inJustDecodeBounds = false;
        return BitmapFactory.decodeFile(filePath, options);
    }

    /**
     * 从输入流加载位图。
     * <p>
     * 此方法旨在将给定的输入流转换为位图对象。输入流可以是任何包含图像数据的流资源，例如从网络、文件或资源包中获取的流。
     *
     * @param is 输入流，包含要加载的位图数据。如果输入流为null，则方法将直接返回null。
     * @return 返回解码后的位图对象。如果输入流为null或解码过程中发生错误，则返回null。
     */
    public static Bitmap getBitmap(final InputStream is) {
        if (is == null) return null;
        return BitmapFactory.decodeStream(is);
    }

    /**
     * 从输入流中加载位图，并根据指定的最大宽度和高度进行缩放。
     * 此方法通过计算缩放比例，避免加载过大的图片导致内存问题。
     *
     * @param is        图片的输入流。
     * @param maxWidth  图片加载后的最大宽度。
     * @param maxHeight 图片加载后的最大高度。
     * @return 缩放后的位图对象，如果输入流为空则返回null。
     */
    public static Bitmap getBitmap(final InputStream is, final int maxWidth, final int maxHeight) {
        if (is == null) return null;
        BitmapFactory.Options options = new BitmapFactory.Options();
        options.inJustDecodeBounds = true;
        BitmapFactory.decodeStream(is, null, options);
        options.inSampleSize = calculateInSampleSize(options, maxWidth, maxHeight);
        options.inJustDecodeBounds = false;
        return BitmapFactory.decodeStream(is, null, options);
    }

    /**
     * 从字节数组加载位图。
     * <p>
     * 此方法用于将给定的字节数组解码为位图对象。如果字节数组为空或长度为0，则返回null。
     * 使用BitmapFactory的decodeByteArray方法进行解码，解码范围从给定的偏移量开始到字节数组的末尾。
     *
     * @param data   字节数组，包含要解码的位图数据。
     * @param offset 字节数组中开始解码的偏移量。
     * @return 解码后的位图对象，如果解码失败或输入数据无效，则返回null。
     */
    public static Bitmap getBitmap(final byte[] data, final int offset) {
        if (data.length == 0) return null;
        return BitmapFactory.decodeByteArray(data, offset, data.length);
    }

    /**
     * 从字节数组中加载位图，并根据指定的最大宽度和最大高度进行缩放。
     * <p>
     * 此方法首先计算位图的缩放比例，以确保加载的位图不会超过指定的最大尺寸。
     * 它通过设置inSampleSize来减少内存使用，这对于处理大型图像特别有用。
     *
     * @param data      位图数据的字节数组。
     * @param offset    字节数组中开始读取数据的偏移量。
     * @param maxWidth  加载后位图的最大宽度。
     * @param maxHeight 加载后位图的最大高度。
     * @return 加载并缩放后的位图对象，如果数据为空或无法加载，则返回null。
     */
    public static Bitmap getBitmap(final byte[] data,
                                   final int offset,
                                   final int maxWidth,
                                   final int maxHeight) {
        if (data.length == 0) return null;
        BitmapFactory.Options options = new BitmapFactory.Options();
        options.inJustDecodeBounds = true;
        BitmapFactory.decodeByteArray(data, offset, data.length, options);
        options.inSampleSize = calculateInSampleSize(options, maxWidth, maxHeight);
        options.inJustDecodeBounds = false;
        return BitmapFactory.decodeByteArray(data, offset, data.length, options);
    }

    /**
     * 根据资源ID加载位图。
     * <p>
     * 该方法通过资源ID获取Drawable对象，并将其转换为Bitmap。如果给定的资源ID无法找到对应的Drawable，
     * 则返回null。这个方法主要用于在不直接操作UI组件的情况下，从资源文件中加载位图。
     *
     * @param resId 资源ID，指定要加载的位图资源。
     * @return 加载成功的位图对象，如果无法加载则返回null。
     */
    public static Bitmap getBitmap(@DrawableRes final int resId) {
        Drawable drawable = ContextCompat.getDrawable(MyAppThis.getThis(), resId);
        if (drawable == null) {
            return null;
        }
        Canvas canvas = new Canvas();
        Bitmap bitmap = Bitmap.createBitmap(drawable.getIntrinsicWidth(),
                drawable.getIntrinsicHeight(),
                Bitmap.Config.ARGB_8888);
        canvas.setBitmap(bitmap);
        drawable.setBounds(0, 0, drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight());
        drawable.draw(canvas);
        return bitmap;
    }

    /**
     * 根据资源ID、最大宽度和最大高度加载并缩放位图。
     * 此方法旨在优化大图加载，避免内存溢出。通过计算缩放比例，减小加载到内存中的图片大小。
     *
     * @param resId     图片资源的ID。
     * @param maxWidth  加载后图片的最大宽度。
     * @param maxHeight 加载后图片的最大高度。
     * @return 缩放后的Bitmap对象。
     */
    public static Bitmap getBitmap(@DrawableRes final int resId,
                                   final int maxWidth,
                                   final int maxHeight) {
        BitmapFactory.Options options = new BitmapFactory.Options();
        final Resources resources = MyAppThis.getThis().getResources();
        options.inJustDecodeBounds = true;
        BitmapFactory.decodeResource(resources, resId, options);
        options.inSampleSize = calculateInSampleSize(options, maxWidth, maxHeight);
        options.inJustDecodeBounds = false;
        return BitmapFactory.decodeResource(resources, resId, options);
    }

    /**
     * 从文件描述符加载位图。
     * <p>
     * 此方法尝试从提供的文件描述符中解码位图。文件描述符通常是由文件、套接字等打开的操作系统资源的抽象表示。
     * 使用文件描述符可以避免文件路径字符串的潜在安全问题，并且可能在某些情况下提供更好的性能。
     *
     * @param fd 文件描述符，指向要加载的位图文件。
     * @return 如果加载成功，返回解码后的Bitmap对象；如果加载失败或输入参数为空，返回null。
     */
    public static Bitmap getBitmap(final FileDescriptor fd) {
        if (fd == null) return null;
        return BitmapFactory.decodeFileDescriptor(fd);
    }

    /**
     * 根据指定的文件描述符加载并缩放位图(Bitmap)。
     * 这个方法旨在优化大图的加载，避免内存溢出。它通过计算缩放比例，只加载位图的一个子集。
     *
     * @param fd        文件描述符，指向要加载的图片。
     * @param maxWidth  所加载图片的最大宽度。
     * @param maxHeight 所加载图片的最大高度。
     * @return 缩放后的位图对象，如果加载失败或参数无效，则返回null。
     */
    public static Bitmap getBitmap(final FileDescriptor fd,
                                   final int maxWidth,
                                   final int maxHeight) {
        if (fd == null) return null;
        BitmapFactory.Options options = new BitmapFactory.Options();
        /*设置inJustDecodeBounds为true，此时不会实际加载图片，但会计算出图片的原始尺寸*/
        options.inJustDecodeBounds = true;
        BitmapFactory.decodeFileDescriptor(fd, null, options);
        options.inSampleSize = calculateInSampleSize(options, maxWidth, maxHeight);
        /*设置inJustDecodeBounds为false，现在将实际加载图片*/
        options.inJustDecodeBounds = false;
        return BitmapFactory.decodeFileDescriptor(fd, null, options);
    }

    /**
     * 根据指定的颜色给位图着色。
     * 该方法用于将原始位图的颜色替换为指定的颜色，通过在新位图上绘制指定颜色实现。
     *
     * @param src   原始位图，不能为null。
     * @param color 指定的颜色，使用ARGB格式的整数表示。
     * @return 返回着色后的位图。如果原始位图为空，则返回null。
     */
    public static Bitmap drawColor(@NonNull final Bitmap src, @ColorInt final int color) {
        return drawColor(src, color, false);
    }

    /**
     * 根据指定的颜色给位图着色。
     * 该方法用于将原始位图的颜色替换为指定的颜色，通过在新位图上绘制指定颜色实现。
     * 如果recycle参数为真，原位图将被回收以避免内存泄漏。
     *
     * @param src     原始位图，不能为null。
     * @param color   指定的颜色，使用ARGB格式的整数表示。
     * @param recycle 指示是否应该回收原始位图的标志。
     * @return 返回着色后的位图。如果原始位图为空，则返回null。
     */
    public static Bitmap drawColor(@NonNull final Bitmap src,
                                   @ColorInt final int color,
                                   final boolean recycle) {
        if (isEmptyBitmap(src)) return null;
        Bitmap ret = recycle ? src : src.copy(src.getConfig(), true);
        Canvas canvas = new Canvas(ret);
        /*使用指定的颜色和混合模式（深色模式）填充画布，实现位图颜色的改变*/
        canvas.drawColor(color, PorterDuff.Mode.DARKEN);
        return ret;
    }

    /**
     * 根据指定的新宽度和新高度按比例缩放图片。
     *
     * @param src       原始Bitmap图片。
     * @param newWidth  缩放后图片的宽度。
     * @param newHeight 缩放后图片的高度。
     * @return 缩放后的Bitmap图片，如果原始图片为空则返回null。
     */
    public static Bitmap scale(final Bitmap src, final int newWidth, final int newHeight) {
        return scale(src, newWidth, newHeight, false);
    }

    /**
     * 根据指定的新宽度和新高度按比例缩放图片。
     * 如果源图片为空，则返回null。
     * 如果recycle参数为true且条件允许，将回收源图片以释放内存。
     *
     * @param src       原始Bitmap图片。
     * @param newWidth  缩放后图片的宽度。
     * @param newHeight 缩放后图片的高度。
     * @param recycle   是否回收原始Bitmap。
     * @return 缩放后的Bitmap图片，如果原始图片为空则返回null。
     */
    public static Bitmap scale(final Bitmap src,
                               final int newWidth,
                               final int newHeight,
                               final boolean recycle) {
        if (isEmptyBitmap(src)) return null;
        Bitmap ret = Bitmap.createScaledBitmap(src, newWidth, newHeight, true);
        if (recycle && !src.isRecycled() && ret != src) src.recycle();
        return ret;
    }

    /**
     * 缩放指定的Bitmap对象。
     *
     * @param src         待缩放的Bitmap对象。
     * @param scaleWidth  缩放后的宽度比例, sx：水平方向的缩放因子。值大于1表示放大，小于1表示缩小，等于1表示保持原大小
     * @param scaleHeight 缩放后的高度比例。
     * @return 缩放后的Bitmap对象，如果原始Bitmap为空，则返回null。
     */
    public static Bitmap scale(final Bitmap src, final float scaleWidth, final float scaleHeight) {
        return scale(src, scaleWidth, scaleHeight, false);
    }

    /**
     * 缩放指定的Bitmap对象。
     * <p>
     * 此方法用于根据指定的宽高比例对Bitmap进行缩放。如果原始Bitmap为空，则直接返回null。
     * 如果recycle参数为true且条件允许，方法将尝试回收原始Bitmap以释放资源。
     *
     * @param src         待缩放的Bitmap对象。
     * @param scaleWidth  缩放后的宽度比例, sx：水平方向的缩放因子。值大于1表示放大，小于1表示缩小，等于1表示保持原大小
     * @param scaleHeight 缩放后的高度比例。
     * @param recycle     是否回收原始Bitmap对象。
     * @return 缩放后的Bitmap对象，如果原始Bitmap为空，则返回null。
     */
    public static Bitmap scale(final Bitmap src,
                               final float scaleWidth,
                               final float scaleHeight,
                               final boolean recycle) {
        if (isEmptyBitmap(src)) return null;
        // 创建一个Matrix对象，并设置缩放比例
        Matrix matrix = new Matrix();
        /*setScale(float sx, float sy)*/
        /*sx：水平方向的缩放因子。值大于1表示放大，小于1表示缩小，等于1表示保持原大小*/
        /*sy：垂直方向的缩放因子。作用原理同sx，允许独立控制垂直方向的缩放比例*/
        matrix.setScale(scaleWidth, scaleHeight);
        Bitmap ret = Bitmap.createBitmap(src, 0, 0, src.getWidth(), src.getHeight(), matrix, true);
        if (recycle && !src.isRecycled() && ret != src) src.recycle();
        return ret;
    }

    /**
     * 剪切指定区域的位图。
     *
     * @param src    原始位图，将从中剪切区域。
     * @param x      剪切区域的左上角x坐标。
     * @param y      剪切区域的左上角y坐标。
     * @param width  剪切区域的宽度。
     * @param height 剪切区域的高度。
     * @return 剪切后的位图。如果原始位图为空，则返回null。
     */
    public static Bitmap clip(final Bitmap src,
                              final int x,
                              final int y,
                              final int width,
                              final int height) {
        return clip(src, x, y, width, height, false);
    }

    /**
     * 剪切指定区域的位图。
     * <p>
     * 从原始位图中剪切出一个指定区域，并根据需要回收原始位图。这在处理位图时非常有用，尤其是当需要从一个大位图中提取小块区域时，可以减少内存使用。
     *
     * @param src     原始位图，将从中剪切区域。
     * @param x       剪切区域的左上角x坐标。
     * @param y       剪切区域的左上角y坐标。
     * @param width   剪切区域的宽度。
     * @param height  剪切区域的高度。
     * @param recycle 是否回收原始位图。如果为true且原始位图未被回收，并且剪切出的位图不是原始位图本身，则回收原始位图。
     * @return 剪切后的位图。如果原始位图为空，则返回null。
     */
    public static Bitmap clip(final Bitmap src,
                              final int x,
                              final int y,
                              final int width,
                              final int height,
                              final boolean recycle) {
        if (isEmptyBitmap(src)) return null;
        Bitmap ret = Bitmap.createBitmap(src, x, y, width, height);
        if (recycle && !src.isRecycled() && ret != src) src.recycle();
        return ret;
    }

    /**
     * 对图片进行斜切变换。
     * 通过矩阵变换对图片进行x轴和y轴的斜切操作，斜切中心点为(px, py)。
     *
     * @param src 原始图片。
     * @param kx  水平方向的斜切因子。正值会使图像沿X轴正方向倾斜（右侧上移，左侧下移），负值则相反
     * @param ky  垂直方向的斜切因子。正值会使图像沿Y轴正方向倾斜（顶部右移，底部左移），负值则相反
     * @return 返回斜切后的图片，如果原始图片为空，则返回null。
     */
    public static Bitmap skew(final Bitmap src, final float kx, final float ky) {
        return skew(src, kx, ky, 0, 0, false);
    }

    /**
     * 对图片进行斜切变换。
     * 通过矩阵变换对图片进行x轴和y轴的斜切操作，斜切中心点为(px, py)。
     *
     * @param src     原始图片。
     * @param kx      水平方向的斜切因子。正值会使图像沿X轴正方向倾斜（右侧上移，左侧下移），负值则相反
     * @param ky      垂直方向的斜切因子。正值会使图像沿Y轴正方向倾斜（顶部右移，底部左移），负值则相反
     * @param recycle 是否回收原始图片资源，以避免内存泄漏。
     * @return 返回斜切后的图片，如果原始图片为空，则返回null。
     */
    public static Bitmap skew(final Bitmap src,
                              final float kx,
                              final float ky,
                              final boolean recycle) {
        return skew(src, kx, ky, 0, 0, recycle);
    }

    /**
     * 对图片进行斜切变换。
     * 通过矩阵变换对图片进行x轴和y轴的斜切操作，斜切中心点为(px, py)。
     *
     * @param src 原始图片。
     * @param kx  水平方向的斜切因子。正值会使图像沿X轴正方向倾斜（右侧上移，左侧下移），负值则相反
     * @param ky  垂直方向的斜切因子。正值会使图像沿Y轴正方向倾斜（顶部右移，底部左移），负值则相反
     * @param px  斜切操作的水平中心点的X坐标。默认为0（图像左边缘）
     * @param py  斜切操作的垂直中心点的Y坐标。默认为0（图像上边缘）
     * @return 返回斜切后的图片，如果原始图片为空，则返回null。
     */
    public static Bitmap skew(final Bitmap src,
                              final float kx,
                              final float ky,
                              final float px,
                              final float py) {
        return skew(src, kx, ky, px, py, false);
    }

    /**
     * 对图片进行斜切变换。
     * 通过矩阵变换对图片进行x轴和y轴的斜切操作，斜切中心点为(px, py)。
     *
     * @param src     原始图片。
     * @param kx      水平方向的斜切因子。正值会使图像沿X轴正方向倾斜（右侧上移，左侧下移），负值则相反
     * @param ky      垂直方向的斜切因子。正值会使图像沿Y轴正方向倾斜（顶部右移，底部左移），负值则相反
     * @param px      斜切操作的水平中心点的X坐标。默认为0（图像左边缘）
     * @param py      斜切操作的垂直中心点的Y坐标。默认为0（图像上边缘）
     * @param recycle 是否回收原始图片资源，以避免内存泄漏。
     * @return 返回斜切后的图片，如果原始图片为空，则返回null。
     */
    public static Bitmap skew(final Bitmap src,
                              final float kx,
                              final float ky,
                              final float px,
                              final float py,
                              final boolean recycle) {
        if (isEmptyBitmap(src)) return null;
        Matrix matrix = new Matrix();
        matrix.setSkew(kx, ky, px, py);
        Bitmap ret = Bitmap.createBitmap(src, 0, 0, src.getWidth(), src.getHeight(), matrix, true);
        if (recycle && !src.isRecycled() && ret != src) src.recycle();
        return ret;
    }

    /**
     * 旋转图片。
     *
     * @param src     原始Bitmap图片。
     * @param degrees 旋转的角度，以度为单位。
     * @param px      旋转的中心点的X坐标。
     * @param py      旋转的中心点的Y坐标。
     * @return 旋转后的Bitmap图片。如果原始图片为空或不需要旋转，则可能返回null或原始Bitmap。
     */
    public static Bitmap rotate(final Bitmap src,
                                final int degrees,
                                final float px,
                                final float py) {
        return rotate(src, degrees, px, py, false);
    }

    /**
     * 旋转图片,以图片中心为旋转点。
     *
     * @param src     原始Bitmap图片。
     * @param degrees 旋转的角度，以度为单位。
     * @return 旋转后的Bitmap图片。如果原始图片为空或不需要旋转，则可能返回null或原始Bitmap。
     */
    public static Bitmap rotate(final Bitmap src,
                                final int degrees) {
        int width = src.getWidth();
        int height = src.getHeight();
        return rotate(src, degrees, width / 2, height / 2, false);
    }

    /**
     * 旋转图片。
     *
     * @param src     原始Bitmap图片。
     * @param degrees 旋转的角度，以度为单位。
     * @param px      旋转的中心点的X坐标。
     * @param py      旋转的中心点的Y坐标。
     * @param recycle 是否回收原始Bitmap。如果为true且条件允许，原始Bitmap将被回收。
     * @return 旋转后的Bitmap图片。如果原始图片为空或不需要旋转，则可能返回null或原始Bitmap。
     */
    public static Bitmap rotate(final Bitmap src,
                                final int degrees,
                                final float px,
                                final float py,
                                final boolean recycle) {
        if (isEmptyBitmap(src)) return null;
        if (degrees == 0) return src;
        Matrix matrix = new Matrix();
        matrix.setRotate(degrees, px, py);
        Bitmap ret = Bitmap.createBitmap(src, 0, 0, src.getWidth(), src.getHeight(), matrix, true);
        if (recycle && !src.isRecycled() && ret != src) src.recycle();
        return ret;
    }

    /**
     * 根据文件路径获取图片的旋转角度。
     * 通过读取图片的EXIF信息中的方向标签（ORIENTATION），来确定图片需要旋转的角度。
     * 支持的旋转角度为0、90、180和270度。
     *
     * @param filePath 图片文件的路径。
     * @return 图片的旋转角度。如果读取EXIF信息失败或不支持的旋转角度，则返回0。
     */
    public static int getRotateDegree(final String filePath) {
        try {
            /*使用ExifInterface加载图片文件，以便读取其EXIF信息*/
            ExifInterface exifInterface = new ExifInterface(filePath);
            /*获取图片的方向属性，如果不存在则默认为正常方向*/
            int orientation = exifInterface.getAttributeInt(
                    ExifInterface.TAG_ORIENTATION,
                    ExifInterface.ORIENTATION_NORMAL
            );
            switch (orientation) {
                case ExifInterface.ORIENTATION_ROTATE_90:
                    return 90;
                case ExifInterface.ORIENTATION_ROTATE_180:
                    return 180;
                case ExifInterface.ORIENTATION_ROTATE_270:
                    return 270;
                default:
                    return 0;
            }
        } catch (IOException e) {
            e.printStackTrace();
            return -1;
        }
    }

    /**
     * 将给定的Bitmap转换为圆形Bitmap。
     *
     * @param src 原始Bitmap。
     * @return 圆形Bitmap，如果原始Bitmap为空或宽高为0，则返回null。
     */
    public static Bitmap toRound(final Bitmap src) {
        return toRound(src, 0, 0, false);
    }

    /**
     * 将给定的Bitmap转换为圆形Bitmap。
     *
     * @param src     原始Bitmap。
     * @param recycle 是否回收原始Bitmap。
     * @return 圆形Bitmap，如果原始Bitmap为空或宽高为0，则返回null。
     */
    public static Bitmap toRound(final Bitmap src, final boolean recycle) {
        return toRound(src, 0, 0, recycle);
    }

    /**
     * 将给定的Bitmap转换为圆形Bitmap。
     * <p>
     * 此方法支持为圆形Bitmap添加边框。如果原始Bitmap为空或宽高为0，则返回null。
     * 参数borderSize用于设置边框的宽度，borderColor用于设置边框的颜色。
     *
     * @param src         原始Bitmap。
     * @param borderSize  边框宽度，如果为0则表示无边框。
     * @param borderColor 边框颜色。
     * @return 圆形Bitmap，如果原始Bitmap为空或宽高为0，则返回null。
     */
    public static Bitmap toRound(final Bitmap src,
                                 @IntRange(from = 0) int borderSize,
                                 @ColorInt int borderColor) {
        return toRound(src, borderSize, borderColor, false);
    }

    /**
     * 将给定的Bitmap转换为圆形Bitmap。
     * <p>
     * 此方法支持为圆形Bitmap添加边框。如果原始Bitmap为空或宽高为0，则返回null。
     * 参数borderSize用于设置边框的宽度，borderColor用于设置边框的颜色。
     * 如果recycle参数为true且原始Bitmap不为空且不已被回收，则在返回新生成的Bitmap后回收原始Bitmap。
     *
     * @param src         原始Bitmap。
     * @param borderSize  边框宽度，如果为0则表示无边框。
     * @param borderColor 边框颜色。
     * @param recycle     是否回收原始Bitmap。
     * @return 圆形Bitmap，如果原始Bitmap为空或宽高为0，则返回null。
     */
    public static Bitmap toRound(final Bitmap src,
                                 @IntRange(from = 0) int borderSize,
                                 @ColorInt int borderColor,
                                 final boolean recycle) {
        if (isEmptyBitmap(src)) return null;
        int width = src.getWidth();
        int height = src.getHeight();
        int size = Math.min(width, height);
        Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
        Bitmap ret = Bitmap.createBitmap(width, height, src.getConfig());
        float center = size / 2f;
        RectF rectF = new RectF(0, 0, width, height);
        rectF.inset((width - size) / 2f, (height - size) / 2f);
        Matrix matrix = new Matrix();
        matrix.setTranslate(rectF.left, rectF.top);
        if (width != height) {
            /*如果原始Bitmap的宽高不相等，则进行等比例缩放*/
            matrix.preScale((float) size / width, (float) size / height);
        }
        BitmapShader shader = new BitmapShader(src, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
        shader.setLocalMatrix(matrix);
        paint.setShader(shader);
        Canvas canvas = new Canvas(ret);
        /*绘制圆形区域*/
        canvas.drawRoundRect(rectF, center, center, paint);
        if (borderSize > 0) {
            paint.setShader(null);
            paint.setColor(borderColor);
            paint.setStyle(Paint.Style.STROKE);
            paint.setStrokeWidth(borderSize);
            float radius = center - borderSize / 2f;
            canvas.drawCircle(width / 2f, height / 2f, radius, paint);
        }
        if (recycle && !src.isRecycled() && ret != src) src.recycle();
        return ret;
    }

    /**
     * 将给定的位图转换为圆角位图。
     * 此方法支持设置圆角的半径、边框大小和颜色，并可选择是否回收原始位图。
     *
     * @param src    原始位图。
     * @param radius 边框四个角的圆角半径
     * @return 圆角位图。如果原始位图为null或空位图，则返回null。
     */
    public static Bitmap toRoundCorner(final Bitmap src, final float radius) {
        return toRoundCorner(src, radius, 0, 0, false);
    }

    /**
     * 将给定的位图转换为圆角位图。
     * 此方法支持设置圆角的半径、边框大小和颜色，并可选择是否回收原始位图。
     *
     * @param src     原始位图。
     * @param radius  边框四个角的圆角半径
     * @param recycle 是否回收原始位图。如果为true且原始位图不为空且不是正在使用的位图，则回收它。
     * @return 圆角位图。如果原始位图为null或空位图，则返回null。
     */
    public static Bitmap toRoundCorner(final Bitmap src,
                                       final float radius,
                                       final boolean recycle) {
        return toRoundCorner(src, radius, 0, 0, recycle);
    }

    /**
     * 将给定的位图转换为圆角位图。
     * 此方法支持设置圆角的半径、边框大小和颜色，并可选择是否回收原始位图。
     *
     * @param src         原始位图。
     * @param radius      边框四个角的圆角半径
     * @param borderSize  边框的大小。如果为0，则不绘制边框。
     * @param borderColor 边框的颜色。使用ARGB格式的整数表示。
     * @return 圆角位图。如果原始位图为null或空位图，则返回null。
     */
    public static Bitmap toRoundCorner(final Bitmap src,
                                       final float radius,
                                       @FloatRange(from = 0) float borderSize,
                                       @ColorInt int borderColor) {
        return toRoundCorner(src, radius, borderSize, borderColor, false);
    }

    /**
     * 将给定的位图转换为圆角位图。
     * 此方法支持设置圆角的半径、边框大小和颜色，并可选择是否回收原始位图。
     *
     * @param src         原始位图。
     * @param radii       每个角的圆角半径数组。数组长度应为8，顺序为左上、右上、右下、左下四个角的X和Y半径。
     * @param borderSize  边框的大小。如果为0，则不绘制边框。
     * @param borderColor 边框的颜色。使用ARGB格式的整数表示。
     * @return 圆角位图。如果原始位图为null或空位图，则返回null。
     */
    public static Bitmap toRoundCorner(final Bitmap src,
                                       final float[] radii,
                                       @FloatRange(from = 0) float borderSize,
                                       @ColorInt int borderColor) {
        return toRoundCorner(src, radii, borderSize, borderColor, false);
    }

    /**
     * 将给定的位图转换为圆角位图。
     * 此方法支持设置圆角的半径、边框大小和颜色，并可选择是否回收原始位图。
     *
     * @param src         原始位图。
     * @param radius      边框四个角的圆角半径
     * @param borderSize  边框的大小。如果为0，则不绘制边框。
     * @param borderColor 边框的颜色。使用ARGB格式的整数表示。
     * @param recycle     是否回收原始位图。如果为true且原始位图不为空且不是正在使用的位图，则回收它。
     * @return 圆角位图。如果原始位图为null或空位图，则返回null。
     */
    public static Bitmap toRoundCorner(final Bitmap src,
                                       final float radius,
                                       @FloatRange(from = 0) float borderSize,
                                       @ColorInt int borderColor,
                                       final boolean recycle) {
        float[] radii = {radius, radius, radius, radius, radius, radius, radius, radius};
        return toRoundCorner(src, radii, borderSize, borderColor, recycle);
    }

    /**
     * 将给定的位图转换为圆角位图。
     * 此方法支持设置圆角的半径、边框大小和颜色，并可选择是否回收原始位图。
     *
     * @param src         原始位图。
     * @param radii       每个角的圆角半径数组。数组长度应为8，顺序为左上、右上、右下、左下四个角的X和Y半径。
     * @param borderSize  边框的大小。如果为0，则不绘制边框。
     * @param borderColor 边框的颜色。使用ARGB格式的整数表示。
     * @param recycle     是否回收原始位图。如果为true且原始位图不为空且不是正在使用的位图，则回收它。
     * @return 圆角位图。如果原始位图为null或空位图，则返回null。
     */
    public static Bitmap toRoundCorner(final Bitmap src,
                                       final float[] radii,
                                       @FloatRange(from = 0) float borderSize,
                                       @ColorInt int borderColor,
                                       final boolean recycle) {
        if (isEmptyBitmap(src)) return null;
        int width = src.getWidth();
        int height = src.getHeight();
        Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
        Bitmap ret = Bitmap.createBitmap(width, height, src.getConfig());
        /*使用BitmapShader将原始位图应用于新位图的画笔*/
        BitmapShader shader = new BitmapShader(src, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
        paint.setShader(shader);
        Canvas canvas = new Canvas(ret);
        RectF rectF = new RectF(0, 0, width, height);
        float halfBorderSize = borderSize / 2f;
        rectF.inset(halfBorderSize, halfBorderSize);
        Path path = new Path();
        path.addRoundRect(rectF, radii, Path.Direction.CW);
        canvas.drawPath(path, paint);
        if (borderSize > 0) {
            /*清除画笔的当前着色器，以便可以设置新的颜色*/
            paint.setShader(null);
            paint.setColor(borderColor);
            paint.setStyle(Paint.Style.STROKE);
            paint.setStrokeWidth(borderSize);
            paint.setStrokeCap(Paint.Cap.ROUND);
            canvas.drawPath(path, paint);
        }
        if (recycle && !src.isRecycled() && ret != src) src.recycle();
        return ret;
    }

    /**
     * 给图片添加矩形边框。
     *
     * @param src          原始Bitmap图片。
     * @param borderSize   边框宽度，以像素为单位。
     * @param color        边框颜色。
     * @param cornerRadius 边框四个角的圆角半径。
     * @return 带有边框的Bitmap图片。如果原始Bitmap为空或者出现错误，则返回null。
     */
    public static Bitmap addCornerBorder(final Bitmap src,
                                         @FloatRange(from = 1) final float borderSize,
                                         @ColorInt final int color,
                                         @FloatRange(from = 0) final float cornerRadius) {
        return addBorder(src, borderSize, color, false, cornerRadius, false);
    }

    /**
     * 给图片添加圆角边框。
     *
     * @param src        原始Bitmap图片。
     * @param borderSize 边框宽度，以像素为单位。
     * @param color      边框颜色。
     * @param radii      如果不绘制圆形，用于指定每个角的圆角半径。
     *                   一个float数组，包含8个元素，分别表示每个角的横向和纵向的半径。顺序是左上角、右上角、右下角、左下角
     *                   float[] radii = {20, 20, 0, 0, 0, 0, 30, 30}; 表示：左上角20, 20，右下角30, 30
     * @return 带有边框的Bitmap图片。如果原始Bitmap为空，则返回null。
     */
    public static Bitmap addCornerBorder(final Bitmap src,
                                         @FloatRange(from = 1) final float borderSize,
                                         @ColorInt final int color,
                                         final float[] radii) {
        return addBorder(src, borderSize, color, false, radii, false);
    }

    /**
     * 给图片添加矩形边框。
     *
     * @param src        原始Bitmap图片。
     * @param borderSize 边框宽度，以像素为单位。
     * @param color      边框颜色。
     * @param radii      如果不绘制圆形，用于指定每个角的圆角半径。
     *                   一个float数组，包含8个元素，分别表示每个角的横向和纵向的半径。顺序是左上角、右上角、右下角、左下角
     *                   float[] radii = {20, 20, 0, 0, 0, 0, 30, 30}; 表示：左上角20, 20，右下角30, 30
     * @param recycle    是否回收原始Bitmap。
     * @return 带有边框的Bitmap图片。如果原始Bitmap为空，则返回null。
     */
    public static Bitmap addCornerBorder(final Bitmap src,
                                         @FloatRange(from = 1) final float borderSize,
                                         @ColorInt final int color,
                                         final float[] radii,
                                         final boolean recycle) {
        return addBorder(src, borderSize, color, false, radii, recycle);
    }

    /**
     * 给图片添加矩形边框，支持圆角矩形边框。
     *
     * @param src          原始Bitmap图片。
     * @param borderSize   边框宽度，以像素为单位。
     * @param color        边框颜色。
     * @param cornerRadius 边框四个角的圆角半径，以像素为单位。
     * @param recycle      是否回收原始的Bitmap对象。如果为true，则会回收原始Bitmap，调用者不应再使用它。
     * @return 带有边框的Bitmap图片。如果原始Bitmap为空或者添加边框失败，则返回null。
     */
    public static Bitmap addCornerBorder(final Bitmap src,
                                         @FloatRange(from = 1) final float borderSize,
                                         @ColorInt final int color,
                                         @FloatRange(from = 0) final float cornerRadius,
                                         final boolean recycle) {
        return addBorder(src, borderSize, color, false, cornerRadius, recycle);
    }

    /**
     * 给图片添加圆形边框。
     *
     * @param src        原始Bitmap图片。
     * @param borderSize 边框宽度，以像素为单位。
     * @param color      边框颜色。
     * @return 带有边框的Bitmap图片。如果原始Bitmap为空或者出现错误，则返回null。
     */
    public static Bitmap addCircleBorder(final Bitmap src,
                                         @FloatRange(from = 1) final float borderSize,
                                         @ColorInt final int color) {
        return addBorder(src, borderSize, color, true, 0, false);
    }

    /**
     * 给图片添加圆形边框。
     *
     * @param src        原始Bitmap图片。
     * @param borderSize 边框宽度，以像素为单位。
     * @param color      边框颜色。
     * @param recycle    是否回收原始的Bitmap对象。如果为true，原始Bitmap将被回收，调用者不应再使用它。
     * @return 带有边框的Bitmap图片。如果原始Bitmap为空或者出现错误，则返回null。
     */
    public static Bitmap addCircleBorder(final Bitmap src,
                                         @FloatRange(from = 1) final float borderSize,
                                         @ColorInt final int color,
                                         final boolean recycle) {
        return addBorder(src, borderSize, color, true, 0, recycle);
    }

    /**
     * 给图片添加边框。
     * 该方法是addBorder方法的重载版本，增加了对圆角矩形边框的支持。
     * 通过指定不同的圆角半径，可以实现四个角不同程度的圆角效果。
     *
     * @param src          原始Bitmap图片。
     * @param borderSize   边框宽度，以像素为单位。
     * @param color        边框颜色。
     * @param isCircle     是否将图片处理为圆形。如果为真，则边框将是一个圆；如果为假，则边框保持为矩形。
     * @param cornerRadius 边框四个角的圆角半径。如果isCircle为true，此参数将被忽略。
     * @param recycle      是否回收原始的Bitmap对象。如果为true，原始Bitmap将被回收，调用者不应再使用它。
     * @return 带有边框的Bitmap图片。如果原始Bitmap为空或者出现错误，则返回null。
     */
    private static Bitmap addBorder(final Bitmap src,
                                    @FloatRange(from = 1) final float borderSize,
                                    @ColorInt final int color,
                                    final boolean isCircle,
                                    final float cornerRadius,
                                    final boolean recycle) {
        float[] radii = {cornerRadius, cornerRadius, cornerRadius, cornerRadius,
                cornerRadius, cornerRadius, cornerRadius, cornerRadius};
        return addBorder(src, borderSize, color, isCircle, radii, recycle);
    }

    /**
     * 给图片添加边框。
     *
     * @param src        原始Bitmap图片。
     * @param borderSize 边框宽度，以像素为单位。
     * @param color      边框颜色。
     * @param isCircle   是否将图片处理为圆形。
     * @param radii      如果不绘制圆形，用于指定每个角的圆角半径。
     *                   一个float数组，包含8个元素，分别表示每个角的横向和纵向的半径。顺序是左上角、右上角、右下角、左下角
     *                   float[] radii = {20, 20, 0, 0, 0, 0, 30, 30}; 表示：左上角20, 20，右下角30, 30
     * @param recycle    是否回收原始Bitmap。
     * @return 带有边框的Bitmap图片。如果原始Bitmap为空，则返回null。
     */
    private static Bitmap addBorder(final Bitmap src,
                                    @FloatRange(from = 1) final float borderSize,
                                    @ColorInt final int color,
                                    final boolean isCircle,
                                    final float[] radii,
                                    final boolean recycle) {
        if (isEmptyBitmap(src)) return null;
        Bitmap ret = recycle ? src : src.copy(src.getConfig(), true);
        int width = ret.getWidth();
        int height = ret.getHeight();
        Canvas canvas = new Canvas(ret);
        Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
        paint.setColor(color);
        paint.setStyle(Paint.Style.STROKE);
        paint.setStrokeWidth(borderSize);
        if (isCircle) {
            /*计算圆形的半径，减去边框宽度的一半以确保边框在圆形之外*/
            float radius = Math.min(width, height) / 2f - borderSize / 2f;
            canvas.drawCircle(width / 2f, height / 2f, radius, paint);
        } else {
            RectF rectF = new RectF(0, 0, width, height);
            float halfBorderSize = borderSize / 2f;
            rectF.inset(halfBorderSize, halfBorderSize);
            Path path = new Path();
            /*rect: 表示要绘制的矩形的位置和大小*/
            /*radii: 一个 float 数组，包含8个元素，分别表示每个角的横向和纵向的半径。顺序是左上角、右上角、右下角、左下角*/
            /*dir: 一个 Direction 枚举类型，表示绘制圆角矩形的方向*/
            path.addRoundRect(rectF, radii, Path.Direction.CW);
            canvas.drawPath(path, paint);
        }
        return ret;
    }

    /**
     * 添加反射效果到Bitmap图片。
     * <p>
     * 该方法通过创建一个带有反射效果的新Bitmap，使得原始Bitmap看起来像是在它的下方有另一个模糊的倒影。
     * 反射效果的生成通过翻转原始Bitmap的下半部分，并在其上应用一个透明度渐变来实现。
     *
     * @param src              原始Bitmap图片。
     * @param reflectionHeight 反射部分的高度。
     * @return 带有反射效果的新Bitmap，如果原始Bitmap为空，则返回null。
     */
    public static Bitmap addReflection(final Bitmap src, final int reflectionHeight) {
        return addReflection(src, reflectionHeight, false);
    }

    /**
     * 添加反射效果到Bitmap图片。
     * <p>
     * 该方法通过创建一个带有反射效果的新Bitmap，使得原始Bitmap看起来像是在它的下方有另一个模糊的倒影。
     * 反射效果的生成通过翻转原始Bitmap的下半部分，并在其上应用一个透明度渐变来实现。
     *
     * @param src              原始Bitmap图片。
     * @param reflectionHeight 反射部分的高度。
     * @param recycle          是否回收原始Bitmap。
     * @return 带有反射效果的新Bitmap，如果原始Bitmap为空，则返回null。
     */
    public static Bitmap addReflection(final Bitmap src,
                                       final int reflectionHeight,
                                       final boolean recycle) {
        if (isEmptyBitmap(src)) return null;
        final int REFLECTION_GAP = 0;
        int srcWidth = src.getWidth();
        int srcHeight = src.getHeight();
        Matrix matrix = new Matrix();
        matrix.preScale(1, -1);
        Bitmap reflectionBitmap = Bitmap.createBitmap(src, 0, srcHeight - reflectionHeight,
                srcWidth, reflectionHeight, matrix, false);
        Bitmap ret = Bitmap.createBitmap(srcWidth, srcHeight + reflectionHeight, src.getConfig());
        Canvas canvas = new Canvas(ret);
        canvas.drawBitmap(src, 0, 0, null);
        canvas.drawBitmap(reflectionBitmap, 0, srcHeight + REFLECTION_GAP, null);
        Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
        LinearGradient shader = new LinearGradient(
                0, srcHeight,
                0, ret.getHeight() + REFLECTION_GAP,
                0x70FFFFFF,
                0x00FFFFFF,
                Shader.TileMode.MIRROR);
        paint.setShader(shader);
        paint.setXfermode(new PorterDuffXfermode(android.graphics.PorterDuff.Mode.DST_IN));
        canvas.drawRect(0, srcHeight + REFLECTION_GAP, srcWidth, ret.getHeight(), paint);
        if (!reflectionBitmap.isRecycled()) reflectionBitmap.recycle();
        if (recycle && !src.isRecycled() && ret != src) src.recycle();
        return ret;
    }

    /**
     * 添加文字水印到图片。
     * <p>
     * 在给定的位图上添加文本水印，通过设置文本内容、字体大小、颜色和位置来定制水印。
     * 如果源位图为空或文本内容为空，则不添加水印并返回null。
     *
     * @param src      源位图，将在其上添加水印。
     * @param content  水印的文本内容。
     * @param textSize 文本的大小。
     * @param color    文本的颜色。
     * @param x        文本左上角的x坐标。
     * @param y        文本左上角的y坐标。
     * @return 带有水印的新位图，如果无法添加水印则返回null。
     */
    public static Bitmap addTextWatermark(final Bitmap src,
                                          final String content,
                                          final int textSize,
                                          @ColorInt final int color,
                                          final float x,
                                          final float y) {
        return addTextWatermark(src, content, textSize, color, x, y, false);
    }

    /**
     * 添加文字水印到图片。
     * <p>
     * 在给定的位图上添加文本水印，通过设置文本内容、字体大小、颜色和位置来定制水印。
     * 如果源位图为空或文本内容为空，则不添加水印并返回null。
     * 如果recycle参数为true且条件允许，将回收源位图以释放资源。
     *
     * @param src      源位图，将在其上添加水印。
     * @param content  水印的文本内容。
     * @param textSize 文本的大小。
     * @param color    文本的颜色。
     * @param x        文本左上角的x坐标。
     * @param y        文本左上角的y坐标。
     * @param recycle  是否回收源位图。
     * @return 带有水印的新位图，如果无法添加水印则返回null。
     */
    public static Bitmap addTextWatermark(final Bitmap src,
                                          final String content,
                                          final float textSize,
                                          @ColorInt final int color,
                                          final float x,
                                          final float y,
                                          final boolean recycle) {
        if (isEmptyBitmap(src) || content == null) return null;
        Bitmap ret = src.copy(src.getConfig(), true);
        Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
        Canvas canvas = new Canvas(ret);
        paint.setColor(color);
        paint.setTextSize(textSize);
        Rect bounds = new Rect();
        paint.getTextBounds(content, 0, content.length(), bounds);
        canvas.drawText(content, x, y + textSize, paint);
        if (recycle && !src.isRecycled() && ret != src) src.recycle();
        return ret;
    }

    /**
     * 添加图像水印。
     * 在给定的源图片上添加另一个图片作为水印，允许指定水印的位置和透明度
     *
     * @param src       源图片，即要添加水印的图片。
     * @param watermark 水印图片，即要添加到源图片上的图片。
     * @param x         水印图片在源图片上的X坐标。
     * @param y         水印图片在源图片上的Y坐标。
     * @param alpha     水印图片的透明度，值范围为0-255。
     * @return 带有水印的新图片，如果源图片为空，则返回null。
     */
    public static Bitmap addImageWatermark(final Bitmap src,
                                           final Bitmap watermark,
                                           final int x, final int y,
                                           final int alpha) {
        return addImageWatermark(src, watermark, x, y, alpha, false);
    }

    /**
     * 添加图像水印。
     * 在给定的源图片上添加另一个图片作为水印，允许指定水印的位置和透明度，并可选择是否回收源图片。
     *
     * @param src       源图片，即要添加水印的图片。
     * @param watermark 水印图片，即要添加到源图片上的图片。
     * @param x         水印图片在源图片上的X坐标。
     * @param y         水印图片在源图片上的Y坐标。
     * @param alpha     水印图片的透明度，值范围为0-255。
     * @param recycle   是否回收源图片，如果为true且源图片不为空且与生成的图片不是同一对象，则回收源图片。
     * @return 带有水印的新图片，如果源图片为空，则返回null。
     */
    public static Bitmap addImageWatermark(final Bitmap src,
                                           final Bitmap watermark,
                                           final int x,
                                           final int y,
                                           final int alpha,
                                           final boolean recycle) {
        if (isEmptyBitmap(src)) return null;
        Bitmap ret = src.copy(src.getConfig(), true);
        if (!isEmptyBitmap(watermark)) {
            Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
            Canvas canvas = new Canvas(ret);
            paint.setAlpha(alpha);
            canvas.drawBitmap(watermark, x, y, paint);
        }
        if (recycle && !src.isRecycled() && ret != src) src.recycle();
        return ret;
    }

    /**
     * 将给定的Bitmap转换为只有透明度的Bitmap，
     *
     * @param src 原始Bitmap，将被转换为只有透明度的Bitmap。
     * @return 一个新的Bitmap，仅包含原始Bitmap的透明度信息。 如果原始Bitmap为空，则返回null。
     */
    public static Bitmap toAlpha(final Bitmap src) {
        return toAlpha(src, false);
    }

    /**
     * 将给定的Bitmap转换为只有透明度的Bitmap，
     * 并根据需要回收原始Bitmap。 主要用于处理Bitmap的透明度，而不改变其颜色信息。
     *
     * @param src     原始Bitmap，将被转换为只有透明度的Bitmap。
     * @param recycle 指示是否应该回收原始Bitmap以释放资源的布尔值。
     *                如果为true且条件允许（即src非空，未被回收且与返回的Bitmap不同），则回收src。
     * @return 一个新的Bitmap，仅包含原始Bitmap的透明度信息。 如果原始Bitmap为空，则返回null。
     */
    public static Bitmap toAlpha(final Bitmap src, final Boolean recycle) {
        if (isEmptyBitmap(src)) return null;
        Bitmap ret = src.extractAlpha();
        if (recycle && !src.isRecycled() && ret != src) src.recycle();
        return ret;
    }

    /**
     * 将Bitmap图片转换为灰度图。
     *
     * @param src 源Bitmap图片。
     * @return 转换后的灰度Bitmap，如果源图片为空则返回null。
     */
    public static Bitmap toGray(final Bitmap src) {
        return toGray(src, false);
    }

    /**
     * 将Bitmap图片转换为灰度图。
     * <p>
     * 通过设置颜色矩阵(ColorMatrix)的饱和度为0，来实现将彩色图片转换为灰度图的效果。
     * 如果源图片为空，则直接返回null。如果recycle参数为true且源图片未被回收且与目标图片不是同一实例，则回收源图片以释放资源。
     *
     * @param src     源Bitmap图片。
     * @param recycle 是否回收源Bitmap。
     * @return 转换后的灰度Bitmap，如果源图片为空则返回null。
     */
    public static Bitmap toGray(final Bitmap src, final boolean recycle) {
        if (isEmptyBitmap(src)) return null;
        Bitmap ret = Bitmap.createBitmap(src.getWidth(), src.getHeight(), src.getConfig());
        Canvas canvas = new Canvas(ret);
        Paint paint = new Paint();
        ColorMatrix colorMatrix = new ColorMatrix();
        colorMatrix.setSaturation(0);
        ColorMatrixColorFilter colorMatrixColorFilter = new ColorMatrixColorFilter(colorMatrix);
        paint.setColorFilter(colorMatrixColorFilter);
        canvas.drawBitmap(src, 0, 0, paint);
        if (recycle && !src.isRecycled() && ret != src) src.recycle();
        return ret;
    }

    /**
     * 对位图进行快速模糊处理。
     *
     * @param src    原始位图。
     * @param scale  缩放比例，范围为(0, 1]，值越小位图被缩放得越小。
     * @param radius 模糊半径，范围为(0, 25]，值越大模糊效果越明显。
     * @return 模糊处理后的位图，如果输入位图为null，则返回null。
     */
    public static Bitmap fastBlur(final Bitmap src,
                                  @FloatRange(
                                          from = 0, to = 1, fromInclusive = false
                                  ) final float scale,
                                  @FloatRange(
                                          from = 0, to = 25, fromInclusive = false
                                  ) final float radius) {
        return fastBlur(src, scale, radius, false, false);
    }

    /**
     * 对位图进行快速模糊处理。
     *
     * @param src     原始位图。
     * @param scale   缩放比例，范围为(0, 1]，值越小位图被缩放得越小。
     * @param radius  模糊半径，范围为(0, 25]，值越大模糊效果越明显。
     * @param recycle 是否回收原始位图和中间位图资源，以避免内存泄漏。
     * @return 模糊处理后的位图，如果输入位图为null，则返回null。
     */
    public static Bitmap fastBlur(final Bitmap src,
                                  @FloatRange(
                                          from = 0, to = 1, fromInclusive = false
                                  ) final float scale,
                                  @FloatRange(
                                          from = 0, to = 25, fromInclusive = false
                                  ) final float radius,
                                  final boolean recycle) {
        return fastBlur(src, scale, radius, recycle, false);
    }

    /**
     * 对位图进行快速模糊处理。
     * 使用RenderScript或堆栈模糊算法对位图进行模糊处理，根据Android系统版本选择不同的模糊实现方式。
     * 支持根据给定的缩放比例对位图进行缩放，并在处理完成后按需回收位图资源。
     *
     * @param src           原始位图。
     * @param scale         缩放比例，范围为(0, 1]，值越小位图被缩放得越小。
     * @param radius        模糊半径，范围为(0, 25]，值越大模糊效果越明显。
     * @param recycle       是否回收原始位图和中间位图资源，以避免内存泄漏。
     * @param isReturnScale 是否返回缩放后的模糊位图，如果为false则返回原始尺寸的模糊位图。
     * @return 模糊处理后的位图，如果输入位图为null，则返回null。
     */
    public static Bitmap fastBlur(final Bitmap src,
                                  @FloatRange(
                                          from = 0, to = 1, fromInclusive = false
                                  ) final float scale,
                                  @FloatRange(
                                          from = 0, to = 25, fromInclusive = false
                                  ) final float radius,
                                  final boolean recycle,
                                  final boolean isReturnScale) {
        if (isEmptyBitmap(src)) return null;
        int width = src.getWidth();
        int height = src.getHeight();
        Matrix matrix = new Matrix();
        matrix.setScale(scale, scale);
        Bitmap scaleBitmap =
                Bitmap.createBitmap(src, 0, 0, src.getWidth(), src.getHeight(), matrix, true);
        Paint paint = new Paint(Paint.FILTER_BITMAP_FLAG | Paint.ANTI_ALIAS_FLAG);
        Canvas canvas = new Canvas();
        PorterDuffColorFilter filter = new PorterDuffColorFilter(
                Color.TRANSPARENT, PorterDuff.Mode.SRC_ATOP);
        paint.setColorFilter(filter);
        canvas.scale(scale, scale);
        canvas.drawBitmap(scaleBitmap, 0, 0, paint);
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
            scaleBitmap = renderScriptBlur(scaleBitmap, radius, recycle);
        } else {
            scaleBitmap = stackBlur(scaleBitmap, (int) radius, recycle);
        }
        if (scale == 1 || isReturnScale) {
            if (recycle && !src.isRecycled() && scaleBitmap != src) src.recycle();
            return scaleBitmap;
        }
        Bitmap ret = Bitmap.createScaledBitmap(scaleBitmap, width, height, true);
        if (!scaleBitmap.isRecycled()) scaleBitmap.recycle();
        if (recycle && !src.isRecycled() && ret != src) src.recycle();
        return ret;
    }

    /**
     * 使用RenderScript对位图进行模糊处理。
     *
     * @param src    原始位图，将被模糊处理。
     * @param radius 模糊半径，范围为0到25（不包括0和25），值越大模糊效果越明显。
     * @return 模糊处理后的位图。
     */
    @RequiresApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
    public static Bitmap renderScriptBlur(final Bitmap src,
                                          @FloatRange(
                                                  from = 0, to = 25, fromInclusive = false
                                          ) final float radius) {
        return renderScriptBlur(src, radius, false);
    }

    /**
     * 使用RenderScript对位图进行模糊处理。
     * <p>
     * 该方法通过RenderScript的ScriptIntrinsicBlur intrinsic来实现位图的模糊效果。RenderScript是一种用于提高计算性能的Android编程模型，
     * 特别适合用于图像处理等计算密集型任务。
     *
     * @param src     原始位图，将被模糊处理。
     * @param radius  模糊半径，范围为0到25（不包括0和25），值越大模糊效果越明显。
     * @param recycle 是否回收原始位图。如果为true，原始位图将被回收以避免内存泄漏；如果为false，将返回一份原始位图的复制品并对其进行处理。
     * @return 模糊处理后的位图。
     */
    @RequiresApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
    public static Bitmap renderScriptBlur(final Bitmap src,
                                          @FloatRange(
                                                  from = 0, to = 25, fromInclusive = false
                                          ) final float radius,
                                          final boolean recycle) {
        RenderScript rs = null;
        Bitmap ret = recycle ? src : src.copy(src.getConfig(), true);
        try {
            rs = RenderScript.create(MyAppThis.getThis());
            rs.setMessageHandler(new RenderScript.RSMessageHandler());
            Allocation input = Allocation.createFromBitmap(rs,
                    ret,
                    Allocation.MipmapControl.MIPMAP_NONE,
                    Allocation.USAGE_SCRIPT);
            Allocation output = Allocation.createTyped(rs, input.getType());
            ScriptIntrinsicBlur blurScript = ScriptIntrinsicBlur.create(rs, Element.U8_4(rs));
            blurScript.setInput(input);
            blurScript.setRadius(radius);
            blurScript.forEach(output);
            output.copyTo(ret);
        } finally {
            if (rs != null) {
                rs.destroy();
            }
        }
        return ret;
    }

    /**
     * 对指定图片进行模糊处理，使用栈blur算法
     *
     * @param src    原始Bitmap图片
     * @param radius 模糊半径，取值范围0-25，小于1时自动取1
     * @return 模糊处理后的Bitmap图片
     */
    public static Bitmap stackBlur(final Bitmap src, final int radius) {
        return stackBlur(src, radius, false);
    }

    /**
     * 对指定图片进行模糊处理，使用栈blur算法
     *
     * @param src     原始Bitmap图片
     * @param radius  模糊半径，取值范围0-25，小于1时自动取1
     * @param recycle 是否回收原始Bitmap，true表示回收，false表示不回收
     * @return 模糊处理后的Bitmap图片
     */
    public static Bitmap stackBlur(final Bitmap src, int radius, final boolean recycle) {
        Bitmap ret = recycle ? src : src.copy(src.getConfig(), true);
        if (radius < 1) {
            radius = 1;
        }
        int w = ret.getWidth();
        int h = ret.getHeight();

        int[] pix = new int[w * h];
        ret.getPixels(pix, 0, w, 0, 0, w, h);

        int wm = w - 1;
        int hm = h - 1;
        int wh = w * h;
        int div = radius + radius + 1;

        int r[] = new int[wh];
        int g[] = new int[wh];
        int b[] = new int[wh];
        int rsum, gsum, bsum, x, y, i, p, yp, yi, yw;
        int vmin[] = new int[Math.max(w, h)];

        int divsum = (div + 1) >> 1;
        divsum *= divsum;
        int dv[] = new int[256 * divsum];
        for (i = 0; i < 256 * divsum; i++) {
            dv[i] = (i / divsum);
        }

        yw = yi = 0;

        int[][] stack = new int[div][3];
        int stackpointer;
        int stackstart;
        int[] sir;
        int rbs;
        int r1 = radius + 1;
        int routsum, goutsum, boutsum;
        int rinsum, ginsum, binsum;

        for (y = 0; y < h; y++) {
            rinsum = ginsum = binsum = routsum = goutsum = boutsum = rsum = gsum = bsum = 0;
            for (i = -radius; i <= radius; i++) {
                p = pix[yi + Math.min(wm, Math.max(i, 0))];
                sir = stack[i + radius];
                sir[0] = (p & 0xff0000) >> 16;
                sir[1] = (p & 0x00ff00) >> 8;
                sir[2] = (p & 0x0000ff);
                rbs = r1 - Math.abs(i);
                rsum += sir[0] * rbs;
                gsum += sir[1] * rbs;
                bsum += sir[2] * rbs;
                if (i > 0) {
                    rinsum += sir[0];
                    ginsum += sir[1];
                    binsum += sir[2];
                } else {
                    routsum += sir[0];
                    goutsum += sir[1];
                    boutsum += sir[2];
                }
            }
            stackpointer = radius;

            for (x = 0; x < w; x++) {

                r[yi] = dv[rsum];
                g[yi] = dv[gsum];
                b[yi] = dv[bsum];

                rsum -= routsum;
                gsum -= goutsum;
                bsum -= boutsum;

                stackstart = stackpointer - radius + div;
                sir = stack[stackstart % div];

                routsum -= sir[0];
                goutsum -= sir[1];
                boutsum -= sir[2];

                if (y == 0) {
                    vmin[x] = Math.min(x + radius + 1, wm);
                }
                p = pix[yw + vmin[x]];

                sir[0] = (p & 0xff0000) >> 16;
                sir[1] = (p & 0x00ff00) >> 8;
                sir[2] = (p & 0x0000ff);

                rinsum += sir[0];
                ginsum += sir[1];
                binsum += sir[2];

                rsum += rinsum;
                gsum += ginsum;
                bsum += binsum;

                stackpointer = (stackpointer + 1) % div;
                sir = stack[(stackpointer) % div];

                routsum += sir[0];
                goutsum += sir[1];
                boutsum += sir[2];

                rinsum -= sir[0];
                ginsum -= sir[1];
                binsum -= sir[2];

                yi++;
            }
            yw += w;
        }
        for (x = 0; x < w; x++) {
            rinsum = ginsum = binsum = routsum = goutsum = boutsum = rsum = gsum = bsum = 0;
            yp = -radius * w;
            for (i = -radius; i <= radius; i++) {
                yi = Math.max(0, yp) + x;

                sir = stack[i + radius];

                sir[0] = r[yi];
                sir[1] = g[yi];
                sir[2] = b[yi];

                rbs = r1 - Math.abs(i);

                rsum += r[yi] * rbs;
                gsum += g[yi] * rbs;
                bsum += b[yi] * rbs;

                if (i > 0) {
                    rinsum += sir[0];
                    ginsum += sir[1];
                    binsum += sir[2];
                } else {
                    routsum += sir[0];
                    goutsum += sir[1];
                    boutsum += sir[2];
                }

                if (i < hm) {
                    yp += w;
                }
            }
            yi = x;
            stackpointer = radius;
            for (y = 0; y < h; y++) {
                // Preserve alpha channel: ( 0xff000000 & pix[yi] )
                pix[yi] = (0xff000000 & pix[yi]) | (dv[rsum] << 16) | (dv[gsum] << 8) | dv[bsum];

                rsum -= routsum;
                gsum -= goutsum;
                bsum -= boutsum;

                stackstart = stackpointer - radius + div;
                sir = stack[stackstart % div];

                routsum -= sir[0];
                goutsum -= sir[1];
                boutsum -= sir[2];

                if (x == 0) {
                    vmin[y] = Math.min(y + r1, hm) * w;
                }
                p = x + vmin[y];

                sir[0] = r[p];
                sir[1] = g[p];
                sir[2] = b[p];

                rinsum += sir[0];
                ginsum += sir[1];
                binsum += sir[2];

                rsum += rinsum;
                gsum += ginsum;
                bsum += binsum;

                stackpointer = (stackpointer + 1) % div;
                sir = stack[stackpointer];

                routsum += sir[0];
                goutsum += sir[1];
                boutsum += sir[2];

                rinsum -= sir[0];
                ginsum -= sir[1];
                binsum -= sir[2];

                yi += w;
            }
        }
        ret.setPixels(pix, 0, w, 0, 0, w, h);
        return ret;
    }

    /**
     * Save the bitmap.
     *
     * @param src      The source of bitmap.
     * @param filePath The path of file.
     * @param format   The format of the image.
     * @return {@code true}: success<br>{@code false}: fail
     */
    public static boolean save(final Bitmap src,
                               final String filePath,
                               final CompressFormat format) {
        return save(src, filePath, format, 100, false);
    }

    /**
     * Save the bitmap.
     *
     * @param src    The source of bitmap.
     * @param file   The file.
     * @param format The format of the image.
     * @return {@code true}: success<br>{@code false}: fail
     */
    public static boolean save(final Bitmap src, final File file, final CompressFormat format) {
        return save(src, file, format, 100, false);
    }

    /**
     * Save the bitmap.
     *
     * @param src      The source of bitmap.
     * @param filePath The path of file.
     * @param format   The format of the image.
     * @param recycle  True to recycle the source of bitmap, false otherwise.
     * @return {@code true}: success<br>{@code false}: fail
     */
    public static boolean save(final Bitmap src,
                               final String filePath,
                               final CompressFormat format,
                               final boolean recycle) {
        return save(src, filePath, format, 100, recycle);
    }

    /**
     * Save the bitmap.
     *
     * @param src     The source of bitmap.
     * @param file    The file.
     * @param format  The format of the image.
     * @param recycle True to recycle the source of bitmap, false otherwise.
     * @return {@code true}: success<br>{@code false}: fail
     */
    public static boolean save(final Bitmap src,
                               final File file,
                               final CompressFormat format,
                               final boolean recycle) {
        return save(src, file, format, 100, recycle);
    }

    /**
     * Save the bitmap.
     *
     * @param src      The source of bitmap.
     * @param filePath The path of file.
     * @param format   The format of the image.
     * @param quality  Hint to the compressor, 0-100. 0 meaning compress for
     *                 small size, 100 meaning compress for max quality. Some
     *                 formats, like PNG which is lossless, will ignore the
     *                 quality setting
     * @return {@code true}: success<br>{@code false}: fail
     */
    public static boolean save(final Bitmap src,
                               final String filePath,
                               final CompressFormat format,
                               final int quality) {
        return save(src, FileUtils.getFileByPath(filePath), format, quality, false);
    }

    /**
     * Save the bitmap.
     *
     * @param src    The source of bitmap.
     * @param file   The file.
     * @param format The format of the image.
     * @return {@code true}: success<br>{@code false}: fail
     */
    public static boolean save(final Bitmap src,
                               final File file,
                               final CompressFormat format,
                               final int quality) {
        return save(src, file, format, quality, false);
    }

    /**
     * Save the bitmap.
     *
     * @param src      The source of bitmap.
     * @param filePath The path of file.
     * @param format   The format of the image.
     * @param quality  Hint to the compressor, 0-100. 0 meaning compress for
     *                 small size, 100 meaning compress for max quality. Some
     *                 formats, like PNG which is lossless, will ignore the
     *                 quality setting
     * @param recycle  True to recycle the source of bitmap, false otherwise.
     * @return {@code true}: success<br>{@code false}: fail
     */
    public static boolean save(final Bitmap src,
                               final String filePath,
                               final CompressFormat format,
                               final int quality,
                               final boolean recycle) {
        return save(src, FileUtils.getFileByPath(filePath), format, quality, recycle);
    }

    /**
     * Save the bitmap.
     *
     * @param src     The source of bitmap.
     * @param file    The file.
     * @param format  The format of the image.
     * @param quality Hint to the compressor, 0-100. 0 meaning compress for
     *                small size, 100 meaning compress for max quality. Some
     *                formats, like PNG which is lossless, will ignore the
     *                quality setting
     * @param recycle True to recycle the source of bitmap, false otherwise.
     * @return {@code true}: success<br>{@code false}: fail
     */
    public static boolean save(final Bitmap src,
                               final File file,
                               final CompressFormat format,
                               final int quality,
                               final boolean recycle) {
        if (isEmptyBitmap(src)) {
            Log.e("ImageUtils", "bitmap is empty.");
            return false;
        }
        if (src.isRecycled()) {
            Log.e("ImageUtils", "bitmap is recycled.");
            return false;
        }
        if (!FileUtils.createFileByDeleteOldFile(file)) {
            Log.e("ImageUtils", "create or delete file <" + file + "> failed.");
            return false;
        }
        OutputStream os = null;
        boolean ret = false;
        try {
            os = new BufferedOutputStream(new FileOutputStream(file));
            ret = src.compress(format, quality, os);
            if (recycle && !src.isRecycled()) src.recycle();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if (os != null) {
                    os.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return ret;
    }

    /**
     * @param src    The source of bitmap.
     * @param format The format of the image.
     * @return the file if save success, otherwise return null.
     */
    @Nullable
    public static File save2Album(final Bitmap src,
                                  final CompressFormat format) {
        return save2Album(src, "", format, 100, false);
    }

    /**
     * @param src     The source of bitmap.
     * @param format  The format of the image.
     * @param recycle True to recycle the source of bitmap, false otherwise.
     * @return the file if save success, otherwise return null.
     */
    @Nullable
    public static File save2Album(final Bitmap src,
                                  final CompressFormat format,
                                  final boolean recycle) {
        return save2Album(src, "", format, 100, recycle);
    }

    /**
     * @param src     The source of bitmap.
     * @param format  The format of the image.
     * @param quality Hint to the compressor, 0-100. 0 meaning compress for
     *                small size, 100 meaning compress for max quality. Some
     *                formats, like PNG which is lossless, will ignore the
     *                quality setting
     * @return the file if save success, otherwise return null.
     */
    @Nullable
    public static File save2Album(final Bitmap src,
                                  final CompressFormat format,
                                  final int quality) {
        return save2Album(src, "", format, quality, false);
    }

    /**
     * @param src     The source of bitmap.
     * @param format  The format of the image.
     * @param quality Hint to the compressor, 0-100. 0 meaning compress for
     *                small size, 100 meaning compress for max quality. Some
     *                formats, like PNG which is lossless, will ignore the
     *                quality setting
     * @param recycle True to recycle the source of bitmap, false otherwise.
     * @return the file if save success, otherwise return null.
     */
    @Nullable
    public static File save2Album(final Bitmap src,
                                  final CompressFormat format,
                                  final int quality,
                                  final boolean recycle) {
        return save2Album(src, "", format, quality, recycle);
    }

    /**
     * @param src     The source of bitmap.
     * @param dirName The name of directory.
     * @param format  The format of the image.
     * @return the file if save success, otherwise return null.
     */
    @Nullable
    public static File save2Album(final Bitmap src,
                                  final String dirName,
                                  final CompressFormat format) {
        return save2Album(src, dirName, format, 100, false);
    }

    /**
     * @param src     The source of bitmap.
     * @param dirName The name of directory.
     * @param format  The format of the image.
     * @param recycle True to recycle the source of bitmap, false otherwise.
     * @return the file if save success, otherwise return null.
     */
    @Nullable
    public static File save2Album(final Bitmap src,
                                  final String dirName,
                                  final CompressFormat format,
                                  final boolean recycle) {
        return save2Album(src, dirName, format, 100, recycle);
    }

    /**
     * @param src     The source of bitmap.
     * @param dirName The name of directory.
     * @param format  The format of the image.
     * @param quality Hint to the compressor, 0-100. 0 meaning compress for
     *                small size, 100 meaning compress for max quality. Some
     *                formats, like PNG which is lossless, will ignore the
     *                quality setting
     * @return the file if save success, otherwise return null.
     */
    @Nullable
    public static File save2Album(final Bitmap src,
                                  final String dirName,
                                  final CompressFormat format,
                                  final int quality) {
        return save2Album(src, dirName, format, quality, false);
    }

    /**
     * @param src     The source of bitmap.
     * @param dirName The name of directory.
     * @param format  The format of the image.
     * @param quality Hint to the compressor, 0-100. 0 meaning compress for
     *                small size, 100 meaning compress for max quality. Some
     *                formats, like PNG which is lossless, will ignore the
     *                quality setting
     * @param recycle True to recycle the source of bitmap, false otherwise.
     * @return the file if save success, otherwise return null.
     */
    @Nullable
    public static File save2Album(final Bitmap src,
                                  final String dirName,
                                  final CompressFormat format,
                                  final int quality,
                                  final boolean recycle) {
        String safeDirName = TextUtils.isEmpty(dirName) ? MyAppThis.getThis().getPackageName() : dirName;
        String suffix = CompressFormat.JPEG.equals(format) ? "JPG" : format.name();
        String fileName = System.currentTimeMillis() + "_" + quality + "." + suffix;
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
            if (!isGranted(Manifest.permission.WRITE_EXTERNAL_STORAGE)) {
                Log.e("ImageUtils", "save to album need storage permission");
                return null;
            }
            File picDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM);
            File destFile = new File(picDir, safeDirName + "/" + fileName);
            if (!save(src, destFile, format, quality, recycle)) {
                return null;
            }
            FileUtils.notifySystemToScan(destFile);
            return destFile;
        } else {
            ContentValues contentValues = new ContentValues();
            contentValues.put(MediaStore.Images.Media.DISPLAY_NAME, fileName);
            contentValues.put(MediaStore.Images.Media.MIME_TYPE, "image/*");
            Uri contentUri;
            if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
                contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
            } else {
                contentUri = MediaStore.Images.Media.INTERNAL_CONTENT_URI;
            }
            contentValues.put(MediaStore.Images.Media.RELATIVE_PATH, Environment.DIRECTORY_DCIM + "/" + safeDirName);
            contentValues.put(MediaStore.MediaColumns.IS_PENDING, 1);
            Uri uri = MyAppThis.getThis().getContentResolver().insert(contentUri, contentValues);
            if (uri == null) {
                return null;
            }
            OutputStream os = null;
            try {
                os = MyAppThis.getThis().getContentResolver().openOutputStream(uri);
                src.compress(format, quality, os);

                contentValues.clear();
                contentValues.put(MediaStore.MediaColumns.IS_PENDING, 0);
                MyAppThis.getThis().getContentResolver().update(uri, contentValues, null, null);

                return UriUtils.uri2File(uri);
            } catch (Exception e) {
                MyAppThis.getThis().getContentResolver().delete(uri, null, null);
                e.printStackTrace();
                return null;
            } finally {
                try {
                    if (os != null) {
                        os.close();
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    private static boolean isGranted(final String permission) {
        return Build.VERSION.SDK_INT < Build.VERSION_CODES.M
                || PackageManager.PERMISSION_GRANTED
                == ContextCompat.checkSelfPermission(MyAppThis.getThis(), permission);
    }

    /**
     * Return whether it is a image according to the file name.
     *
     * @param file The file.
     * @return {@code true}: yes<br>{@code false}: no
     */
    public static boolean isImage(final File file) {
        if (file == null || !file.exists()) {
            return false;
        }
        return isImage(file.getPath());
    }

    /**
     * Return whether it is a image according to the file name.
     *
     * @param filePath The path of file.
     * @return {@code true}: yes<br>{@code false}: no
     */
    public static boolean isImage(final String filePath) {
        try {
            BitmapFactory.Options options = new BitmapFactory.Options();
            options.inJustDecodeBounds = true;
            BitmapFactory.decodeFile(filePath, options);
            return options.outWidth > 0 && options.outHeight > 0;
        } catch (Exception e) {
            return false;
        }
    }

    /**
     * Return the type of image.
     *
     * @param filePath The path of file.
     * @return the type of image
     */
    public static ImageType getImageType(final String filePath) {
        return getImageType(FileUtils.getFileByPath(filePath));
    }

    /**
     * 判断图片类型
     *
     * @param file 图片路径.
     * @return 图片类型{@link ImageType}
     */
    public static ImageType getImageType(final File file) {
        if (file == null) return null;
        InputStream is = null;
        try {
            is = new FileInputStream(file);
            ImageType type = getImageType(is);
            if (type != null) {
                return type;
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if (is != null) {
                    is.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return null;
    }

    private static ImageType getImageType(final InputStream is) {
        if (is == null) return null;
        try {
            byte[] bytes = new byte[12];
            return is.read(bytes) != -1 ? getImageType(bytes) : null;
        } catch (IOException e) {
            e.printStackTrace();
            return null;
        }
    }

    private static ImageType getImageType(final byte[] bytes) {
        String type = ConvertUtils.bytes2HexString(bytes).toUpperCase();
        if (type.contains("FFD8FF")) {
            return ImageType.TYPE_JPG;
        } else if (type.contains("89504E47")) {
            return ImageType.TYPE_PNG;
        } else if (type.contains("47494638")) {
            return ImageType.TYPE_GIF;
        } else if (type.contains("49492A00") || type.contains("4D4D002A")) {
            return ImageType.TYPE_TIFF;
        } else if (type.contains("424D")) {
            return ImageType.TYPE_BMP;
        } else if (type.startsWith("52494646") && type.endsWith("57454250")) {//524946461c57000057454250-12个字节
            return ImageType.TYPE_WEBP;
        } else if (type.contains("00000100") || type.contains("00000200")) {
            return ImageType.TYPE_ICO;
        } else {
            return ImageType.TYPE_UNKNOWN;
        }
    }

    private static boolean isJPEG(final byte[] b) {
        return b.length >= 2
                && (b[0] == (byte) 0xFF) && (b[1] == (byte) 0xD8);
    }

    private static boolean isGIF(final byte[] b) {
        return b.length >= 6
                && b[0] == 'G' && b[1] == 'I'
                && b[2] == 'F' && b[3] == '8'
                && (b[4] == '7' || b[4] == '9') && b[5] == 'a';
    }

    private static boolean isPNG(final byte[] b) {
        return b.length >= 8
                && (b[0] == (byte) 137 && b[1] == (byte) 80
                && b[2] == (byte) 78 && b[3] == (byte) 71
                && b[4] == (byte) 13 && b[5] == (byte) 10
                && b[6] == (byte) 26 && b[7] == (byte) 10);
    }

    /**
     * 判断是否是BMP格式图片
     *
     * @param b
     * @return
     */
    private static boolean isBMP(final byte[] b) {
        return b.length >= 2
                && (b[0] == 0x42) && (b[1] == 0x4d);
    }

    /**
     * 判断bitmap是否为空。
     *
     * @param src bitmap对象
     * @return
     */
    private static boolean isEmptyBitmap(final Bitmap src) {
        return src == null || src.getWidth() == 0 || src.getHeight() == 0;
    }

    ///////////////////////////////////////////////////////////////////////////
    // about compress
    ///////////////////////////////////////////////////////////////////////////

    /**
     * 根据指定的新宽度和新高度按比例缩放图片。
     *
     * @param src       原始Bitmap图片。
     * @param newWidth  缩放后图片的宽度。
     * @param newHeight 缩放后图片的高度。
     * @return 缩放后的Bitmap图片，如果原始图片为空则返回null。
     */
    public static Bitmap compressByScale(final Bitmap src,
                                         final int newWidth,
                                         final int newHeight) {
        return scale(src, newWidth, newHeight, false);
    }

    /**
     * 根据指定的新宽度和新高度按比例缩放图片。
     *
     * @param src       原始Bitmap图片。
     * @param newWidth  缩放后图片的宽度。
     * @param newHeight 缩放后图片的高度。
     * @param recycle   是否回收原始Bitmap。
     * @return 缩放后的Bitmap图片，如果原始图片为空则返回null。
     */
    public static Bitmap compressByScale(final Bitmap src,
                                         final int newWidth,
                                         final int newHeight,
                                         final boolean recycle) {
        return scale(src, newWidth, newHeight, recycle);
    }

    /**
     * 使用比例来缩放指定的Bitmap对象。
     *
     * @param src         待缩放的Bitmap对象。
     * @param scaleWidth  缩放后的宽度比例, sx：水平方向的缩放因子。值大于1表示放大，小于1表示缩小，等于1表示保持原大小
     * @param scaleHeight 缩放后的高度比例。
     * @return 缩放后的Bitmap对象，如果原始Bitmap为空，则返回null。
     */
    public static Bitmap compressByScale(final Bitmap src,
                                         final float scaleWidth,
                                         final float scaleHeight) {
        return scale(src, scaleWidth, scaleHeight, false);
    }

    /**
     * 使用比例来缩放指定的Bitmap对象。
     *
     * @param src         待缩放的Bitmap对象。
     * @param scaleWidth  缩放后的宽度比例, sx：水平方向的缩放因子。值大于1表示放大，小于1表示缩小，等于1表示保持原大小
     * @param scaleHeight 缩放后的高度比例。
     * @param recycle     是否回收原始Bitmap对象。
     * @return 缩放后的Bitmap对象，如果原始Bitmap为空，则返回null。
     */
    public static Bitmap compressByScale(final Bitmap src,
                                         final float scaleWidth,
                                         final float scaleHeight,
                                         final boolean recycle) {
        return scale(src, scaleWidth, scaleHeight, recycle);
    }

    /**
     * 根据指定的质量压缩Bitmap，并返回压缩后的字节数组。
     *
     * @param src     待压缩的Bitmap对象。
     * @param quality 压缩质量，取值范围为0到100，质量越小文件尺寸越小。
     * @return 压缩后的字节数组，如果源Bitmap为空，则返回null。
     */
    public static byte[] compressByQuality(final Bitmap src,
                                           @IntRange(from = 0, to = 100) final int quality) {
        return compressByQuality(src, quality, false);
    }

    /**
     * 根据指定的质量压缩Bitmap，并返回压缩后的字节数组。
     * 如果源Bitmap为空，则返回null。
     * 压缩格式为JPEG，因为该方法主要用于图片压缩，JPEG是常见的图片压缩格式之一。
     *
     * @param src     待压缩的Bitmap对象。
     * @param quality 压缩质量，取值范围为0到100，质量越小文件尺寸越小。
     * @param recycle 是否回收源Bitmap，以释放内存。
     * @return 压缩后的字节数组，如果源Bitmap为空，则返回null。
     */
    public static byte[] compressByQuality(final Bitmap src,
                                           @IntRange(from = 0, to = 100) final int quality,
                                           final boolean recycle) {
        if (isEmptyBitmap(src)) return null;
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        src.compress(Bitmap.CompressFormat.JPEG, quality, baos);
        byte[] bytes = baos.toByteArray();
        if (recycle && !src.isRecycled()) src.recycle();
        return bytes;
    }

    /**
     * 根据质量压缩位图并返回压缩后的字节数组。
     * 压缩目标是使位图的字节大小不超过maxByteSize，同时保持尽可能高的质量。
     *
     * @param src         要压缩的位图。
     * @param maxByteSize 压缩后的最大字节大小。
     * @return 压缩后的字节数组。如果无法压缩或输入无效，则返回空数组。
     */
    public static byte[] compressByQuality(final Bitmap src, final long maxByteSize) {
        return compressByQuality(src, maxByteSize, false);
    }

    /**
     * 根据质量压缩位图并返回压缩后的字节数组。
     * 压缩目标是使位图的字节大小不超过maxByteSize，同时保持尽可能高的质量。
     *
     * @param src         要压缩的位图。
     * @param maxByteSize 压缩后的最大字节大小。
     * @param recycle     是否回收源位图以释放内存。
     * @return 压缩后的字节数组。如果无法压缩或输入无效，则返回空数组。
     */
    public static byte[] compressByQuality(final Bitmap src,
                                           final long maxByteSize,
                                           final boolean recycle) {
        if (isEmptyBitmap(src) || maxByteSize <= 0) return new byte[0];
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        /*尝试以100%的质量压缩位图，这可能是最大的文件大小*/
        src.compress(CompressFormat.JPEG, 100, baos);
        byte[] bytes;
        if (baos.size() <= maxByteSize) {
            bytes = baos.toByteArray();
        } else {
            baos.reset();
            src.compress(CompressFormat.JPEG, 0, baos);
            if (baos.size() >= maxByteSize) {
                bytes = baos.toByteArray();
            } else {
                // find the best quality using binary search
                int st = 0;
                int end = 100;
                int mid = 0;
                while (st < end) {
                    mid = (st + end) / 2;
                    baos.reset();
                    src.compress(CompressFormat.JPEG, mid, baos);
                    int len = baos.size();
                    if (len == maxByteSize) {
                        break;
                    } else if (len > maxByteSize) {
                        end = mid - 1;
                    } else {
                        st = mid + 1;
                    }
                }
                if (end == mid - 1) {
                    baos.reset();
                    src.compress(CompressFormat.JPEG, st, baos);
                }
                bytes = baos.toByteArray();
            }
        }
        if (recycle && !src.isRecycled()) src.recycle();
        return bytes;
    }

    /**
     * 根据采样大小压缩位图。
     * 该方法通过设置采样大小来减少位图的内存使用，采样大小越大，位图越小。
     *
     * @param src        待压缩的源位图。
     * @param sampleSize 采样大小，用于减少内存使用。
     * @return 压缩后的位图，如果源位图为空，则返回null。
     */
    public static Bitmap compressBySampleSize(final Bitmap src, final int sampleSize) {
        return compressBySampleSize(src, sampleSize, false);
    }

    /**
     * 根据采样大小压缩位图。
     * 该方法通过设置采样大小来减少位图的内存使用，采样大小越大，位图越小。
     * 如果recycle参数为真且源位图未被回收，则尝试回收源位图以释放资源。
     *
     * @param src        待压缩的源位图。
     * @param sampleSize 采样大小，用于减少内存使用。
     * @param recycle    指示是否应该回收源位图的标志。
     * @return 压缩后的位图，如果源位图为空，则返回null。
     */
    public static Bitmap compressBySampleSize(final Bitmap src,
                                              final int sampleSize,
                                              final boolean recycle) {
        if (isEmptyBitmap(src)) return null;
        BitmapFactory.Options options = new BitmapFactory.Options();
        options.inSampleSize = sampleSize;
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        src.compress(Bitmap.CompressFormat.JPEG, 100, baos);
        byte[] bytes = baos.toByteArray();
        if (recycle && !src.isRecycled()) src.recycle();
        return BitmapFactory.decodeByteArray(bytes, 0, bytes.length, options);
    }

    /**
     * 根据采样率压缩图片。
     * 该方法通过计算采样率来减小图片的大小，以适应指定的最大宽度和高度。它首先通过不加载图片到内存中来获取图片的原始尺寸，
     * 然后计算出合适的采样率以减少内存使用。如果指定，它还会回收原始Bitmap以帮助管理内存。
     *
     * @param src       原始Bitmap。
     * @param maxWidth  指定的最大宽度。
     * @param maxHeight 指定的最大高度。
     * @return 压缩后的Bitmap，如果原始Bitmap为空，则返回null。
     */
    public static Bitmap compressBySampleSize(final Bitmap src,
                                              final int maxWidth,
                                              final int maxHeight) {
        return compressBySampleSize(src, maxWidth, maxHeight, false);
    }

    /**
     * 根据采样率压缩图片{@link android.graphics.BitmapFactory.Options#inSampleSize}。
     * 该方法通过计算采样率来减小图片的大小，以适应指定的最大宽度和高度。
     * 它首先通过不加载图片到内存中来获取图片的原始尺寸，
     * 然后计算出合适的采样率以减少内存使用。如果指定，它还会回收原始Bitmap以帮助管理内存。
     *
     * @param src       原始Bitmap。
     * @param maxWidth  指定的最大宽度。
     * @param maxHeight 指定的最大高度。
     * @param recycle   指示是否应该回收原始Bitmap的标志。
     * @return 压缩后的Bitmap，如果原始Bitmap为空，则返回null。
     */
    public static Bitmap compressBySampleSize(final Bitmap src,
                                              final int maxWidth,
                                              final int maxHeight,
                                              final boolean recycle) {
        if (isEmptyBitmap(src)) return null;
        BitmapFactory.Options options = new BitmapFactory.Options();
        options.inJustDecodeBounds = true;
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        src.compress(Bitmap.CompressFormat.JPEG, 100, baos);
        byte[] bytes = baos.toByteArray();
        BitmapFactory.decodeByteArray(bytes, 0, bytes.length, options);
        options.inSampleSize = calculateInSampleSize(options, maxWidth, maxHeight);
        options.inJustDecodeBounds = false;
        if (recycle && !src.isRecycled()) src.recycle();
        return BitmapFactory.decodeByteArray(bytes, 0, bytes.length, options);
    }

    /**
     * 获取图片的宽度和高度。
     * <p>
     * 该方法用于不加载图片到内存中，仅通过读取图片文件的元数据来获取图片的宽度和高度。
     * 这种方式可以在处理大型图片时避免内存溢出的问题，因为它避免了将整个图片加载到内存中。
     *
     * @param filePath 图片文件路径。如果文件为null，则返回一个包含0的数组。
     * @return 包含图片宽度和高度的数组，顺序为宽度在前，高度在后。如果文件为null，返回{0, 0}。
     */
    public static int[] getSize(String filePath) {
        return getSize(FileUtils.getFileByPath(filePath));
    }

    /**
     * 获取图片的宽度和高度。
     * <p>
     * 该方法用于不加载图片到内存中，仅通过读取图片文件的元数据来获取图片的宽度和高度。
     * 这种方式可以在处理大型图片时避免内存溢出的问题，因为它避免了将整个图片加载到内存中。
     *
     * @param file 图片文件。如果文件为null，则返回一个包含0的数组。
     * @return 包含图片宽度和高度的数组，顺序为宽度在前，高度在后。如果文件为null，返回{0, 0}。
     */
    public static int[] getSize(File file) {
        if (file == null) return new int[]{0, 0};
        BitmapFactory.Options opts = new BitmapFactory.Options();
        opts.inJustDecodeBounds = true;
        BitmapFactory.decodeFile(file.getAbsolutePath(), opts);
        return new int[]{opts.outWidth, opts.outHeight};
    }

    /**
     * 计算图片的采样大小，以降低图片的解码尺寸，减少内存使用。该采用率为{@link android.graphics.BitmapFactory.Options#inSampleSize}
     * 该方法通过不断减小图片的高度和宽度，直到它们都小于指定的最大值，
     * 以此来确定合适的采样大小。采样大小将以2的幂进行增加。
     *
     * @param options   BitmapFactory.Options对象，包含未解码图片的尺寸信息。
     * @param maxWidth  指定的最大宽度。
     * @param maxHeight 指定的最大高度。
     * @return 返回计算出的采样大小，用于解码图片。
     */
    public static int calculateInSampleSize(final BitmapFactory.Options options,
                                            final int maxWidth,
                                            final int maxHeight) {
        int height = options.outHeight;
        int width = options.outWidth;
        int inSampleSize = 1;
        while (height > maxHeight || width > maxWidth) {
            height >>= 1;
            width >>= 1;
            inSampleSize <<= 1;
        }
        return inSampleSize;
    }

    public enum ImageType {
        TYPE_JPG("jpg"),

        TYPE_PNG("png"),

        TYPE_GIF("gif"),

        TYPE_TIFF("tiff"),

        TYPE_BMP("bmp"),

        TYPE_WEBP("webp"),

        TYPE_ICO("ico"),

        TYPE_UNKNOWN("unknown");

        String value;

        ImageType(String value) {
            this.value = value;
        }

        public String getValue() {
            return value;
        }
    }

}
